pwn with glibc heap(堆利用手冊)


前言

​ 對一些有趣的堆相關的漏洞的利用做一個記錄,如有差錯,請見諒。

​ 文中未做說明 均是指 glibc 2.23

​ 相關引用已在文中進行了標注,如有遺漏,請提醒。

簡單源碼分析

​ 本節只是簡單跟讀了一下 mallocfree 的源碼, 說的比較簡單,很多細節還是要自己拿一份源代碼來讀。

堆中的一些數據結構

堆管理結構

struct malloc_state {
 mutex_t mutex;                 /* Serialize access. */
 int flags;                       /* Flags (formerly in max_fast). */
 #if THREAD_STATS
 /* Statistics for locking. Only used if THREAD_STATS is defined. */
 long stat_lock_direct, stat_lock_loop, stat_lock_wait;
 #endif
 mfastbinptr fastbins[NFASTBINS];    /* Fastbins */
 mchunkptr top;
 mchunkptr last_remainder;
 mchunkptr bins[NBINS * 2];
 unsigned int binmap[BINMAPSIZE];   /* Bitmap of bins */
 struct malloc_state *next;           /* Linked list */
 INTERNAL_SIZE_T system_mem;
 INTERNAL_SIZE_T max_system_mem;
 };
  • malloc_state結構是我們最常用的結構,其中的重要字段如下:
  • fastbins:存儲多個鏈表。每個鏈表由空閑的fastbin組成,是fastbin freelist
  • toptop chunk,指向的是arena中剩下的空間。如果各種freelist都為空,則從top chunk開始分配堆塊。
  • bins:存儲多個雙向鏈表。意義上和堆塊頭部的雙向鏈表一樣,並和其組成了一個雙向環狀空閑列表(freelist)。這里的bins位於freelist的結構上的頭部,后向指針(bk)指向freelist邏輯上的第一個節點。分配chunk時從邏輯上的第一個節點分配尋找合適大小的堆塊。

堆塊結構


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;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

  • prev_size相鄰的前一個堆塊大小。這個字段只有在前一個堆塊(且該堆塊為normal chunk)處於釋放狀態時才有意義。這個字段最重要(甚至是唯一)的作用就是用於堆塊釋放時快速和相鄰的前一個空閑堆塊融合。該字段不計入當前堆塊的大小計算。在前一個堆塊不處於空閑狀態時,數據為前一個堆塊中用戶寫入的數據。libc這么做的原因主要是可以節約4個字節的內存空間,但為了這點空間效率導致了很多安全問題。
  • size:本堆塊的長度。長度計算方式:size字段長度+用戶申請的長度+對齊。libc以 size_T 長度*2 為粒度對齊。例如 32bit 以 4*2=8byte 對齊,64bit8*2=0×10 對齊。因為最少以8字節對齊,所以size一定是8的倍數,故size字段的最后三位恆為0,libc用這三個bit做標志flag。比較關鍵的是最后一個bit(pre_inuse),用於指示相鄰的前一個堆塊是alloc還是free。如果正在使用,則 bit=1。libc判斷 當前堆塊是否處於free狀態的方法 就是 判斷下一個堆塊的 pre_inuse 是否為 1 。這里也是 double freenull byte offset 等漏洞利用的關鍵。
  • fd &bk:雙向指針,用於組成一個雙向空閑鏈表。故這兩個字段只有在堆塊free后才有意義。堆塊在alloc狀態時,這兩個字段內容是用戶填充的數據。兩個字段可以造成內存泄漏(libc的bss地址),Dw shoot等效果
  • 值得一提的是,堆塊根據大小,libc使用fastbin、chunk等邏輯上的結構代表,但其存儲結構上都是malloc_chunk結構,只是各個字段略有區別,如fastbin相對於chunk,不使用bk這個指針,因為fastbin freelist是個單向鏈表。

來源
Libc堆管理機制及漏洞利用技術

Malloc 源碼分析

用戶調用 malloc 時會先進入 __libc_malloc

void *
__libc_malloc (size_t bytes)
{
  mstate ar_ptr;
  void *victim;

  void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))// 如果設置了 __malloc_hook 就執行然后返回
    return (*hook)(bytes, RETURN_ADDRESS (0));

  arena_get (ar_ptr, bytes);

  victim = _int_malloc (ar_ptr, bytes);
  return victim;
}

如果設置了 __malloc_hook 就執行它然后返回, 否則進入 _int_malloc 這個函數就是 malloc 的具體實現

static void *
_int_malloc (mstate av, size_t bytes)
{
   /*
     計算出實際需要的大小,大小按照 2 * size_t 對齊, 64位: 0x10
     所以如個 malloc(0x28) ----> nb = 0x30, 0x10 header + 0x20 當前塊 + 0x8 下一塊的 pre_size
   */

  checked_request2size (bytes, nb);

  /* 
    如果是第一次觸發 malloc, 就會調用 sysmalloc---> mmap 分配內存返回
     */
  if (__glibc_unlikely (av == NULL))
    {
      void *p = sysmalloc (nb, av);
      if (p != NULL)
    alloc_perturb (p, bytes);
      return p;
    }

首先把傳入的 bytes 轉換為 chunk 的實際大小,保存到 nb 里面。然后如果是第一次調用 malloc , 就會進入 sysmalloc 分配內存。

搜索Fastbin

接着會看申請的 nb 是不是在 fastbin 里面,如果是進入 fastbin 的處理流程

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
    {
      idx = fastbin_index (nb);  // 找到nb 對應的 fastbin 的 索引 idx
      mfastbinptr *fb = &fastbin (av, idx);// 找到對應的 fastbin 的指針
      mchunkptr pp = *fb;
      do
        {
          victim = pp;
          if (victim == NULL)
            break;
        }
      while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
             != victim);
      if (victim != 0) //如果 fastbin 非空,就進入這里
        {
          if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))// 判斷大小是否滿足 fastbin相應bin的大小要求
            {
              errstr = "malloc(): memory corruption (fast)";
            errout:
              malloc_printerr (check_action, errstr, chunk2mem (victim), av);
              return NULL;
            }
          check_remalloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }

首先根據 nb 找到該大小對應的 fastbin 的項, 然后看看該 fastbin 是不是為空,如果非空,就分配該 fastbin 的第一個 chunk 給用戶。

分配過程還會檢查待分配的 chunksize 是不是滿足在該 fastbin 項的限制。

fastbin_index (chunksize (victim)) != idx

搜索Smallbin

如果 fastbin 為空或者 nb 不在 fastbin 里面,就會進入 smallbinlargebin 的處理邏輯

if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);//  找到 smallbin 索引
      bin = bin_at (av, idx);
      if ((victim = last (bin)) != bin) // 判斷 bin 中是不是有 chunk
        {
          if (victim == 0) /* initialization check */
            malloc_consolidate (av);
          else
            {
              bck = victim->bk;
    if (__glibc_unlikely (bck->fd != victim)) // 鏈表檢查
                {
                  errstr = "malloc(): smallbin double linked list corrupted";
                  goto errout;
                }
              set_inuse_bit_at_offset (victim, nb); //設置下一個chunk的 in_use 位
              bin->bk = bck;
              bck->fd = bin;

              if (av != &main_arena)
                victim->size |= NON_MAIN_ARENA;
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }
        }
    }

  /*
     大內存分配,進入 malloc_consolidate
   */
  else
    {
      idx = largebin_index (nb);
      if (have_fastchunks (av))
        malloc_consolidate (av);
    }

如果申請的 nb 位於 smallbin 的范圍,就會 fastbin 一樣去找對應的項,然后判斷 bin 是不是為空,如果不空, 分配第一個 chunk 給用戶,分配之前還會校驗該 chunk 是不是正確的。如果為空,就會進入 unsorted bin 的處理了。

__glibc_unlikely (bck->fd != victim)

如果 nb 不滿足 smallbin ,就會觸發 malloc_consolidate . 然后進入 unsorted bin

搜索Unsorted bin

      int iters = 0;
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 遍歷 unsorted bin
        {
          bck = victim->bk;
          size = chunksize (victim);

          if (in_smallbin_range (nb) &&
              bck == unsorted_chunks (av) &&
              victim == av->last_remainder &&
              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
            {
              /* split and reattach remainder */
              remainder_size = size - nb;
              remainder = chunk_at_offset (victim, nb);
              unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
              av->last_remainder = remainder;
              remainder->bk = remainder->fd = unsorted_chunks (av);
              if (!in_smallbin_range (remainder_size))
                {
                  remainder->fd_nextsize = NULL;
                  remainder->bk_nextsize = NULL;
                }

              set_head (victim, nb | PREV_INUSE |
                        (av != &main_arena ? NON_MAIN_ARENA : 0));
              set_head (remainder, remainder_size | PREV_INUSE);
              set_foot (remainder, remainder_size);

              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

遍歷 unsorted bin , 如果此時的 unsorted bin 只有一項,且他就是 av->last_remainder ,同時大小滿足

 (unsigned long) (size) > (unsigned long) (nb + MINSIZE)

就對當前 unsorted bin 進行切割,然后返回切割后的 unsorted bin

否則就先把該 unsorted binunsorted list 中移除下來,這里用了一個 類似 unlink 的操作,不過沒有檢查 chunk 的指針

          /*先摘下該 unsorted bin  */
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);
          
          // 如果申請的大小和該 unsorted bin的大小剛好相等,就直接返回
          if (size == nb)
            {
              set_inuse_bit_at_offset (victim, size);
              if (av != &main_arena)
                victim->size |= NON_MAIN_ARENA;
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

如果申請的大小和該 unsorted bin 的大小剛好相等,就直接返回, 否則就把它放到相應的 bin 里面去。


          if (in_smallbin_range (size))
            {
              victim_index = smallbin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
            }
          else
            {
              victim_index = largebin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
              .......
              .......

如果 sizesmallbin 里就放到 smallbin ,否則就放到 large bin

搜索 Largebin

接下來就會去搜索 largebin

      if (!in_smallbin_range (nb))
        {
          bin = bin_at (av, idx);

          /* skip scan if empty or largest chunk is too small */
          if ((victim = first (bin)) != bin &&
              (unsigned long) (victim->size) >= (unsigned long) (nb))
            {
              victim = victim->bk_nextsize;
              while (((unsigned long) (size = chunksize (victim)) <
                      (unsigned long) (nb)))
                victim = victim->bk_nextsize;

              /* Avoid removing the first entry for a size so that the skip
                 list does not have to be rerouted.  */
              if (victim != last (bin) && victim->size == victim->fd->size)
                victim = victim->fd;

              remainder_size = size - nb;
              unlink (av, victim, bck, fwd);

              /* Exhaust */
              if (remainder_size < MINSIZE)
                {
                  set_inuse_bit_at_offset (victim, size);
                  if (av != &main_arena)
                    victim->size |= NON_MAIN_ARENA;
                }
              /* Split */
              else
                {
                  remainder = chunk_at_offset (victim, nb);
                  /* We cannot assume the unsorted list is empty and therefore
                     have to perform a complete insert here.  */
                  bck = unsorted_chunks (av);
                  fwd = bck->fd;
      if (__glibc_unlikely (fwd->bk != bck))
                    {
                      errstr = "malloc(): corrupted unsorted chunks";
                      goto errout;
                    }
                  remainder->bk = bck;
                  remainder->fd = fwd;
                  bck->fd = remainder;
                  fwd->bk = remainder;
                  if (!in_smallbin_range (remainder_size))
                    {
                      remainder->fd_nextsize = NULL;
                      remainder->bk_nextsize = NULL;
                    }
                  set_head (victim, nb | PREV_INUSE |
                            (av != &main_arena ? NON_MAIN_ARENA : 0));
                  set_head (remainder, remainder_size | PREV_INUSE);
                  set_foot (remainder, remainder_size);
                }
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }
        }

使用 Top chunk


      victim = av->top;
      size = chunksize (victim);
      // 如果 top chunk 大小足夠大就從 top chunk 里面分配
      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
        {
          remainder_size = size - nb;
          remainder = chunk_at_offset (victim, nb);
          av->top = remainder;
          set_head (victim, nb | PREV_INUSE |
                    (av != &main_arena ? NON_MAIN_ARENA : 0));
          set_head (remainder, remainder_size | PREV_INUSE);

          check_malloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }

      /* When we are using atomic ops to free fast chunks we can get
         here for all block sizes.  */
      else if (have_fastchunks (av))
        {
          malloc_consolidate (av);
          /* restore original bin index */
          if (in_smallbin_range (nb))
            idx = smallbin_index (nb);
          else
            idx = largebin_index (nb);
        }

      /*
         Otherwise, relay to handle system-dependent cases
       */
      else
        {
          void *p = sysmalloc (nb, av);
          if (p != NULL)
            alloc_perturb (p, bytes);
          return p;
        }
    }

如果 top chunk 的大小足夠就直接切割分配,否則如果此時還有 fastbin 就觸發 malloc_consolidate 重復上述流程,如果沒有 fastbin 調用 sysmalloc 分配內存

Free 源碼分析

_GI___libc_free

首先是 _GI___libc_free

void __fastcall _GI___libc_free(void *ptr)
{
  if ( _free_hook )
  {
    _free_hook(ptr, retaddr);
  }
  else if ( ptr )
  {
    v1 = (unsigned __int64)ptr - 16;
    v2 = *((_QWORD *)ptr - 1);
    if ( v2 & 2 )                               // 判斷size位,判斷是不是 mmap 獲得的 chunk
    {
      if ( !mp_.no_dyn_threshold
        && v2 > mp_.mmap_threshold
        && v2 <= 0x2000000
        && (v1 < (unsigned __int64)dumped_main_arena_start || v1 >= (unsigned __int64)dumped_main_arena_end) )
      {
        mp_.mmap_threshold = v2 & 0xFFFFFFFFFFFFFFF8LL;
        mp_.trim_threshold = 2 * (v2 & 0xFFFFFFFFFFFFFFF8LL);
      }
      munmap_chunk((mchunkptr)((char *)ptr - 16));
    }
    else
    {
      av = &main_arena;
      if ( v2 & 4 )
        av = *(malloc_state **)(v1 & 0xFFFFFFFFFC000000LL);
      int_free(av, (mchunkptr)v1, 0);
    }
  }
}

如果存在 free_hook , 就會直接調用 free_hook(ptr) 然后返回。否則判斷被 free 的 內存是否是 mmap 獲取的 ,如果是則使用 munmap_chunk 回收內存,否則進入 _int_free

_int_free

首先會做一些簡單的檢查

  size = chunksize (p);

  //檢查指針是否正常,對齊
  if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
      || __builtin_expect (misaligned_chunk (p), 0))
    {
      errstr = "free(): invalid pointer";
    errout:
      if (!have_lock && locked)
        (void) mutex_unlock (&av->mutex);
      malloc_printerr (check_action, errstr, chunk2mem (p), av);
      return;
    }

// 檢查 size 是否 >= MINSIZE ,且是否對齊
  if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
    {
      errstr = "free(): invalid size";
      goto errout;
    }

// 檢查 chunk 是否處於 inuse 狀態
  check_inuse_chunk(av, p);


檢查

  • 指針是否對齊
  • 塊的大小是否對齊,且大於最小的大小
  • 塊是否在 inuse 狀態

進入 fastbin

 if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())) {
    if (have_lock
        || ({ assert (locked == 0);
          mutex_lock(&av->mutex);
          locked = 1;
          chunk_at_offset (p, size)->size <= 2 * SIZE_SZ // next->size <= 2 * SIZE_SZ
            || chunksize (chunk_at_offset (p, size)) >= av->system_mem; // 
          }))
      {
        errstr = "free(): invalid next size (fast)";
        goto errout;
      }

    set_fastchunks(av);
    unsigned int idx = fastbin_index(size);
    fb = &fastbin (av, idx);

    mchunkptr old = *fb, old2;
    unsigned int old_idx = ~0u;
    do
      {
    
    if (__builtin_expect (old == p, 0))
      {
        errstr = "double free or corruption (fasttop)";
        goto errout;
      }
    if (have_lock && old != NULL)
      old_idx = fastbin_index(chunksize(old));
    p->fd = old2 = old; // 插入 fastbin
      }
    while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);

    if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0))
      {
    errstr = "invalid fastbin entry (free)";
    goto errout;
      }
  }

如果 size 滿足 fastbin 的條件,則首先判斷 next_chunk->size 要滿足

next_chunk->size > 2 * SIZE_SZ
next_chunk->size < av->system_mem

接着就會找對相應的 fastbin ,然后插入 該 bin 的第一項。插入前有一個檢查

if (__builtin_expect (old == p, 0))
      {
        errstr = "double free or corruption (fasttop)";
        goto errout;
      }

就是 p->size 索引到的 fastbin 的第一個指針不能和當前的 p 相同,否則會被認為是 double free

進入 Unsorted bin

如果被 free 的這個塊不是 通過 mmap 獲得的,就會進入下面的邏輯


  else if (!chunk_is_mmapped(p)) {
    if (! have_lock) {
      (void)mutex_lock(&av->mutex);
      locked = 1;
    }
    
    // 得到下一個 chunk 的指針
    nextchunk = chunk_at_offset(p, size);

    // 不能 free top chunk
    if (__glibc_unlikely (p == av->top))
      {
        errstr = "double free or corruption (top)";
        goto errout;
      }
   // nextchunk 不能越界,就是限制了 p->size
    if (__builtin_expect (contiguous (av)
              && (char *) nextchunk
              >= ((char *) av->top + chunksize(av->top)), 0))
      {
    errstr = "double free or corruption (out)";
    goto errout;
      }
    /*p 要被標識為 inuse 狀態 */
    if (__glibc_unlikely (!prev_inuse(nextchunk)))
      {
        errstr = "double free or corruption (!prev)";
        goto errout;
      }

    nextsize = chunksize(nextchunk);
    // nextsize 在  [ 2 * SIZE_SZ, av->system_mem] 之間
    if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
    || __builtin_expect (nextsize >= av->system_mem, 0))
      {
        errstr = "free(): invalid next size (normal)";
        goto errout;
      }

    free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);

    /* 如果 p的前一個塊是 free 狀態,就向前合並,通過 p->pre_inused 判斷*/
    if (!prev_inuse(p)) {
      prevsize = p->prev_size;
      size += prevsize;
      p = chunk_at_offset(p, -((long) prevsize));
      unlink(av, p, bck, fwd);
    }

    if (nextchunk != av->top) {
      // 獲得 nextchunk 的下一個 chunk, 的 pre_inused位
      nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

      // 如果 nextchunk 也是 free 狀態的,合並
      if (!nextinuse) {
        unlink(av, nextchunk, bck, fwd);
        size += nextsize;
      } else
    clear_inuse_bit_at_offset(nextchunk, 0);
    
      // 合並的結果放置到 unsorted bin
      bck = unsorted_chunks(av);
      fwd = bck->fd;
      
      // 防止 unsortedbin 被破壞
      if (__glibc_unlikely (fwd->bk != bck))
    {
      errstr = "free(): corrupted unsorted chunks";
      goto errout;
    }
      p->fd = fwd;
      p->bk = bck;
      if (!in_smallbin_range(size))
    {
      p->fd_nextsize = NULL;
      p->bk_nextsize = NULL;
    }
      bck->fd = p;
      fwd->bk = p;

      set_head(p, size | PREV_INUSE);
      set_foot(p, size);

      check_free_chunk(av, p);
    }

    else {
      size += nextsize;
      set_head(p, size | PREV_INUSE);
      av->top = p;
      check_chunk(av, p);
    }


    // 如果 free 得到的 unsorted bin 的 size(包括合並chunk 得到的) 大於等於 FASTBIN_CONSOLIDATION_THRESHOLD 就會觸發 malloc_consolidate
    if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
      if (have_fastchunks(av))
    malloc_consolidate(av);

      if (av == &main_arena) {
#ifndef MORECORE_CANNOT_TRIM
    if ((unsigned long)(chunksize(av->top)) >=
        (unsigned long)(mp_.trim_threshold))
      systrim(mp_.top_pad, av);
#endif
      } else {
    /* Always try heap_trim(), even if the top chunk is not
       large, because the corresponding heap might go away.  */
    heap_info *heap = heap_for_ptr(top(av));

    assert(heap->ar_ptr == av);
    heap_trim(heap, mp_.top_pad);
      }
    }

    if (! have_lock) {
      assert (locked);
      (void)mutex_unlock(&av->mutex);
    }
  }
  /*
    If the chunk was allocated via mmap, release via munmap().
  */

大概流程

  • 首先做了一些檢查, p != top_chunk, p->size 不能越界, 限制了 next_chunk->size, p要處於 inuse狀態(通過 next_chunk->pre_inused 判斷)
  • 接着判斷 p 的前后相鄰塊是不是 free 狀態,如果是就合並
  • 根據此次拿到的 unsorted bin 的 大小,如果 size>=FASTBIN_CONSOLIDATION_THRESHOLD 就會觸發 malloc_consolidate

如果 p 是通過 mmap 獲得的,就通過

 munmap_chunk (p);

釋放掉他

Check In Glbc

函數名 檢查 報錯信息
unlink p->size == nextchunk->pre_size corrupted size vs prev_size
unlink p->fd->bk == p 且 p->bk->fd == p corrupted double-linked list
_int_malloc 當從fastbin分配內存時 ,找到的那個fastbin chunk的size要等於其位於的fastbin 的大小,比如在0x20的 fastbin中其大小就要為0x20 malloc():memory corruption (fast)
_int_malloc 當從 smallbin 分配 chunk( victim) 時, 要求 victim->bk->fd == victim malloc(): smallbin double linked list corrupted
_int_malloc 當迭代 unsorted bin 時 ,迭代中的 chunk (cur)要滿足,cur->size 在 [2*SIZE_SZ, av->system_mem] 中 malloc(): memory corruption
_int_free 當插入一個 chunk 到 fastbin時,判斷fastbin的 head 是不是和 釋放的 chunk 相等 double free or corruption (fasttop)
_int_free 判斷 next_chunk->pre_inuse == 1 double free or corruption (!prev

來源
heap-exploitation

各種漏洞原理及利用

通用的信息泄露思路

chunk 處於 free 狀態時,會進入 bin 里面,其中的 fdbk 可以用於信息泄露

  • 分配兩個 0x90chunk(p0, p1)
  • 釋放掉 p0, p0 會進入 unsorted bin
  • 分配 0x90chunk,再次拿到 p0, 在 malloc 的實現中不會對這些指針進行清空,就可以泄露

如果分配后的內存被 memset 清空后,就需要利用一些其他的漏洞才能利用。

Unsorted bin 用於泄露 libc

fastbin 用於 泄露 heap 地址

原理

在把 chunkbins 拿下來時 會觸發 unlink 操作

/* Take a chunk off a bin list */
#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;             \
              }                                   \
          }                                   \
      }                                       \
}

如果我們可以偽裝 fdbk 過掉 unlink 的檢查,就可以實現 4 字節寫

利用

首先利用其它的漏洞偽造下面的內存布局

image.png

  • p0 = malloc(0x80), p1 = malloc(0x80), ptr = p0
  • 此時 free(p1) ,發現 p1 所在 chunkpre_size = 0 , 表明前一個 chunk 已經 free, 於是向前合並
  • 通過 p1 - 0x10 - 0x80 ( chunk_addr - pre_size ) , 找到前面已經釋放的 chunk , 也就是 我們 偽造的 fake chunk p1
  • 然后進行 unlink, 實現 *ptr = ptr-0x18

Fastbin Attack 總結

原理

Fastbin 在分配 chunk 時,只檢查 p->size&0xfffffffffffff000是否滿足等於的 fastbin的大小 ,而且不檢查指針是否對齊。所以我們只要找到 sizefastbin 的范圍,然后修改 位於 fastbinchunkfd 到這 ,分配幾次以后,就可以分配到這個位置

利用方式

利用 libc 中的 現有的 數據

__malloc_hook 附近

64位下在 __malloc_hook - 0x23 + 0x8 處 的值 為 p64(0x7f) ,這些值可以通過 gdb + hexdump 找找

然后想辦法修改 位於 0x70fastbinchunkfd__malloc_hook - 0x23,然后分配幾次 0x70 的 chunk 就可以修改 __malloc_hook

main_arean->fastbinY 數組

該數組用於存放 指定大小的 fastbin 的表頭指針,如果為空則為 p64(0) , 而堆的地址基本 是 0x5x 開頭的(其在內存就是 xx xx..... 5x), 此時如果在 main_arean->fastbinY 的 相鄰項為 0x0 (相鄰大小的 fastbin), 就會出現 5x 00 00 00... , 所以就可以出現 0x000000000000005x ,可以把它作為 fastbinsize 進行 fastbin attack不過作為 fastbin attack 的 size 不能 為 0x55

於是想辦法修改 位於 0x50fastbinchunkfd__malloc_hook - 0x23,然后分配幾次 0x50 的 chunk 就可以分配到 main_arean, 然后就可以修改 main_arean->top

std* 結構體

std* 類結構體中有很多字段都會被設置為 0x0 , 同時其中的某些字段會有 libc 的地址大多數情況下 libc 是加載在 0x7f.... , 配合着 std* 中的 其他 0x0 的字段,我們就可以有 p64(0x7f) , 然后修改 位於 0x70fastbinchunkfd 為該位置即可。

image.png

自己構造 size

利用 unsorted bin attack 往 __free_hook 構造 size

我們知道如果我們可以 修改 unsorted binfdbk , 在對 unsorted bin 拆卸的 時候 我們就能實現

*(bk + 0x10) = main_arean->unsorted_bin

利用這個我們就能往任意地址寫入 main_arean 的地址, 由於 libc 的地址基本都是 0x7fxxxxx, 所以寫完以后我們就可以在 __free_hook 的前面構造出 p64(0x7f) , 可以作為 fastbin attack 的目標,然后修改 __free_hook

有一個小坑要注意,在 __free_hook-0x30 開始 的 0x30 個字節 是 _IO_stdfile_*_lock 區域,用於 std* 類文件的鎖操作,這個區域的內存會被經常清零

所以 unsorted bin attack 應該往上面一點, 比如 libc.symbols['__free_hook'] - 0x50

還有一點就是在進行 unsorted bin attack 以后 , unsorted bin 鏈表就被破壞了,所以 就只能通過 fastbin 或者 smallbin 進行內存的分配,所以我們應該先劫持 fastbinfd 到 目標位置,然后觸發 unsorted bin attack 寫入 size, 最后進行 fastbin attack ,修改 __free_hook

利用 fastbin 往 main_arean 構造 size
  • 首先分配 0x40chunk p, 然后釋放掉 p ,進入 0x40fastbin

  • 然后通過一些手段,修改 p->fd = p64(0x71)

  • 分配 0x40chunk ,會拿到 p , 此時 main_arean->fastbinY0x40 大小對應的項的值為 p64(0x71)

  • 然后分配 0x71chunk p2, 釋放掉

  • 修改 p2->fdmain_arean->fastbinY 的相應位置,然后分配兩次,即可分配到 main_arean->fastbinY

  • 然后通過修改 main_arean->top, 即可分配到 malloc_hook 或者 free_hook

Unsorted bin Attack

原理

因為 unsorted bin 的取出操作沒有使用 unlink 宏,而是自己實現的幾行代碼

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

所以當我們控制了 victim的 bk 時,則 bk + 0x10 會被改寫成 unsorted bin 的地址,但是 unsorted binbk 也會被破壞,下一次再到這里時就可能因為 victim->bk->fd 不可寫而造成 SIGSEGV

所以在觸發 unsorted bin attack 以后就 只能 通過 fastbinsmallbin 來分配內存了(否則會進入 unsorted bin 的流程,會報錯),所以在 觸發 unsorted bin attack 需要把需要的內存布局好。

利用的方式

寫 stdin->_IO_buf_end

在 glibc中 scanf, gets 等函數默認是對 stdin 結構體進行操作。以 scanf 為例

  • 在調用 scanf 獲取輸入時,首先會把輸入的東西復制到 [_IO_buf_base , _IO_buf_end ], 最大大小為 _IO_buf_end - _IO_buf_base
  • 修改 unsorted binbck_IO_base_end-0x10 ,就可以使 _IO_base_end=main_arens+0x88,我們就能修改很多東西了,而且 malloc_hook 就在這里面
__IO_list_all 和 abort 以及 修改虛表到 _IO_wstrn_jumps
原理

繞過虛表校驗

其實就是對 house of orangelibc2.24 里面的再利用。 在 libc2.24 里對 vtable 進行了校驗。

vtable 進行校驗的函數是 IO_validate_vtable

image.png

就是保證 vtable 要在 __stop___libc_IO_vtables__start___libc_IO_vtables 之間。

這里的目標就是 _IO_wstrn_jumps ,這個也是一個 vtable ,能夠滿足 IO_validate_vtable的校驗。

_IO_wstrn_jumps 有一個有趣的函數 IO_wstr_finish , 位於 libc.symbols['_IO_wstrn_jumps'] + 0x10

void __fastcall IO_wstr_finish(_IO_FILE_2 *fp, int dummy)
{
  _IO_FILE_plus *fp_; // rbx
  wchar_t *io_buf_base; // rdi

  fp_ = fp;
  io_buf_base = fp->_wide_data->_IO_buf_base;
  if ( io_buf_base && !(fp_->file._flags2 & 8) )
    (fp_[1].file._IO_read_ptr)(io_buf_base, *&dummy); // call    qword ptr [fp+0E8h]
  fp_->file._wide_data->_IO_buf_base = 0LL;
  _GI__IO_wdefault_finish(fp_, 0);
}

我們把 fp->_wide_data 改成 fp , 然后設置 fp->_IO_buf_base 設置為 /bin/sh 的地址,fp_[1].file._IO_read_ptr ( fp+0xe8 ) 改成 system 的地址,其他字段根據 check 設置好以便過掉檢查, 之后調用該函數就會 system('/bin/sh')

利用方案舉例

34c3ctf-300 為例, 程序限制只能分配 0x310chunk, 這里利用 unsorted bin 遍歷的缺陷,偽造了一個 0x60smallbin ,為后續做准備。

  • 首先分配 4個 0x310chunk (A X B K) ,釋放 A , B 此時 A , B 均進入 unsorted bin ,並且通過bk 鏈接起來
  • 修改 A->bkfake_bin 的地址,並且 設置 fake_bin->size=0x61 and fake_bin->bk = B, 此時 unsorted bin 的鏈表其實有 3 項
  • 分配 一個 0x310chunk ,此時 A 位於鏈表首部,且大小剛好,分配 A ,並且 把 fake_bin 置於鏈表首部
  • 再次分配 一個 0x310chunk , 此時 fake_bin 位於鏈表首部,大小不夠於是把 fake_bin 放到 smallbin[4] , 然后繼續遍歷 ,分配到 B, 至此 在 smallbin[4] 就存有 fake_bin 的地址

image.png

fake_bin 的 內容為 (從 chunk 的開始地址開始

payload = p64(0xfbad2084)    #偽造的 File 結構體的開始,fp->_flag
payload += p64(0x61)
payload += p64(0xb00bface)  
payload += p64(B_addr) # bk ,設置為 B 的地址
payload += p64(0x0)            # fp->_IO_write_base
payload += p64(libc_base + sh_addr)     # fp->_IO_write_ptr 
payload += p64(libc_base + sh_addr)     # fp->wide_data->buf_base
payload += "A"*60
payload += p64(0x0)            # fp->_flags2
payload += "A"*36
payload += p64(fake_bin)    # fp->_wide_data ,設置為 fake_bin, 復用 fake_bin
payload += "A"*24
payload += p64(0x0)            # fp->_mode
payload += "A"*16
payload += p64( libc.symbols['_IO_wstrn_jumps'] + 0x10 -0x18) # fake vtable
payload += "A"*8
payload += p64(libc_base + libc.symbols['system'])       # ((_IO_strfile *) fp)->_s._free_buffer
  • 然后利用 unsorted bin attack 修改 __IO_list_allmain_arean+88
  • 觸發 abortmalloc_printerr內部會調用), 就會觸發 _IO_flush_all_lockp ,根據 __IO_list_all __chain ,遍歷調用 _IO_OVERFLOW (fp, EOF) ( 其實就是 (fp->vtable + 0x18)(fp, EOF)
  • ___IO_list_all->_chain 位於 smallbin[4] ,所以遍歷第二次可以對 fake_bin 進行 _IO_OVERFLOW (fp, EOF),此時就會調用 IO_wstr_finish, 此時 fake_bin 中的相關數據已經設置好,最后會執行 system("/bin/sh")

參考
34c3ctf-300
Pwn with File結構體 四

組合 fastbin attack
方案一
  • bk 改成 global_max_fast-0x10 觸發 unsorted bin attack 后, global_max_fast會被修改成一個很大的值(指針),所以之后的 內存 分配 和 釋放 都會按 fastbin
  • 之后看情況進行 偽fastbin attack
方案二

bk 改成 libc.symbols['__free_hook'] - 0x50 觸發 unsorted bin attack 后, free_hook 前面就會出現 p64(0x7f) ,之后就可以通過 fastbin attack 修改 free_hook

參考
0ctf-2016-zerostorage

結合 largebin 和 _dl_open_hook
原理

在 遍歷 unsoted bin 時, 是通過 bk 指針 進行遍歷


  for (;; )
    {
      int iters = 0;
      //victim = unsorted_chunks (av)->bk
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 遍歷 unsorted bin
        {
          bck = victim->bk;
        ............
        ............
        ............
          /* remove from unsorted list */
          unsorted_chunks (av)->bk = bck;  //unsorted_chunks (av)->bk =  victim->bk->bk
          bck->fd = unsorted_chunks (av);
          ......
          ......
          ......
        }

所以通過修改 bk 來偽造 unsorted bin 是可行的

同時在 遍歷 unsorted binchunk 放入 largebin 的過程中, 也沒有什么檢查,於是可以利用 把 chunk 放入 largebin 的過程 往 任意地址寫入 chunk 的地址

PS: 因為要偽造 unsorted bin ,需要我們可以拿到 heap 的基地址

大體的思路
  • 在堆上通過修改 unsorted binbk 指針 偽造幾個 unsorted bin(A B C D),(0x400, 0x30, 0x420, 0x30)
  • 分配 0x30 , A 進入 largebin, B 被分配
  • 修改 A->bk = _dl_open_hook - 0x10 and A->bk_nextsize = _dl_open_hook - 0x20
  • 分配 0x30 , C 進入 largebin, 會導致 A->bk->fd = C , A->bk_nextsize->fd_nextsize = C (其實就是 *_dl_open_hook = C)
  • 此時_dl_open_hook 指針被改成 C 的地址, 然后在 C 中設置 p64(libc.symbols['__libc_dlsym'] + 4)+p64(one_gadget)+p64(one_gadget)偽造 dl_open_hook 結構體。
  • 后面的執行過程會調用 _dl_open_hook , 就會調用 __libc_dlsym + 4, 這里面會 跳轉到 dl_open_hook 結構體偏移 8 的值處 , 也就是 one_gadget 的地址

image.png

參考
0ctf 2018 babyheap challenge exploit

特定寫權限的利用

可寫 main_arean

通過一些 fastbin 攻擊, 我們可以分配到 main_arean, 此時一般都是改寫 main_arean->top

轉換為寫 __malloc_hook

malloc_hook -0x10 處存放的是指針,值很大,修改 main_arean->top 到這里,然后控制程序 使得通過 top_chunk 分配, 就可以分配到 malloc_hook

轉換為寫 __free_hook

free_hook-0xb58 處存放的也是一些地址,修改 main_arean->top 到這里,然后控制程序 使得通過 top_chunk 分配幾次內存(一次分配太多,會觸發 sysmalloc, 可以一次分配 0x90 多分配幾次),我們就可以分配到 free_hook

可寫 __malloc_hook

直接寫one_gadget

寫入 one_gadget ,不過觸發的時候,用 malloc_printerr 來觸發 malloc

此時用下面這樣的 one_gadget [rsp+0x50]

0xef6c4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

這樣更穩定,成功率也高

通過 __realloc_hook 中轉

__malloc_hook__realloc_hook 是相鄰的, 且 __realloc_hook__malloc_hook 的前面,所以基本上可以同時修改它們。

利用 one_gadget 時,對於棧的條件會有一些要求,利用 realloc 函數內部的 跳轉 到 __realloc_hook 之前的棧操作,加上棧中原有的數據,可以對棧進行跳轉,以滿足 one_gadget 的要求

realloc         proc near               ; DATA XREF: LOAD:0000000000006BA0↑o

push    r15             
push    r14
push    r13
push    r12
mov     r13, rsi
push    rbp
push    rbx
mov     rbx, rdi
sub     rsp, 38h
mov     rax, cs:__realloc_hook_ptr  #取出 __realloc_hook 指針
mov     rax, [rax]
test    rax, rax
jnz     loc_848E8
test    rsi, rsi
jnz     short loc_846F5
test    rdi, rdi
jnz     loc_84960

代碼中的 push 以及 sub rsp, 38h 都可用於對棧進行調整。

可以收先把 __malloc_hook 設置為 0x6363636363636363, 當程序斷下來后,查看棧的情況,然后選擇跳轉的位置

最后把 malloc_hook 設置為選擇好的位置,realloc_hook 設置為 one_gadget, 觸發 malloc

可寫 __free_hook

直接寫one_gadget

改成 system 函數的地址

然后 釋放掉 內容為 /bin/sh\x00chunk

可寫 std* 結構體

std* 類結構體 定義是 _IO_FILE_plus64 為大小為 0xe0

修改 vtable指針

libc <= 2.23

_IO_FILE_plus 的最后一個字節就是 vtable 指針,修改 vtable 指針到一個可控數據可控的地址,在地址處填上 one_gadget , 然后在調用一些輸入輸出函數時,就會觸發。

如果是堆類題目可以 修改vtable指針到 heap, 或者如果是通過 fastbin 攻擊 分配到了 std* , 那么可以修改 vtable 到 std 的相應位置*, 只要保證 馬上要被調用的函數指針我們可控 即可

libc > 2.23

一般結合 unsorted bin attack ,改到 libc.symbols['_IO_wstrn_jumps'] + 0x10 -0x18, 然后觸發 abort 會調用 _IO_OVERFLOW (fp, EOF) 時就會調用 IO_wstr_finish(fp, EOF) ,通過設置 fp 的數據,就可以 system("/bin/sh").

(: fp為文件結構體的指針

Double Free

原理

程序把指針 free 之后沒有對指針進行清空, 出現了 懸垂指針。后續還可以對該指針進行 free 操作。

利用

基於 pwnable.tw 中的 secretgard

信息泄露

總的思路 : 大塊 拆成 小塊

  • 分配一個 0x120chunk, p1 指向 它。 然后釋放掉他
  • 分配兩個 0x90chunk 重用剛剛 free 掉的 chunk, 可以發現此時 p1==p2
  • 此時再次 free(p1), 在 p2->fd p2->bk 會寫入 main_arean 的地址(free 之后大小大於 fastbin的范圍,進入 unsorted bin )
  • 然后打印 p2 的內容就可以拿到 libc 的地址

image.png

總的思路 : 小塊 融合成 大塊

  • 首先分配兩個 0x90chunk (p0, p1) ,然后釋放掉,會進行合並,形成 一個 0x120unsorted bin
  • 然后分配一個 0x120chunk (p2) , 則 p0=p2 , 此時 p0 所在的 chunk 可以包含 p1chunk
  • 然后在 p0 所在的 chunk 偽造一個 free chunk, 設置好 fdbk , 然后釋放 p1 觸發 unlink
add(0x80) # pz
add(0x80) # p0
add(0x80) # p1
add(0x80) # px

del(1)
del(2)

add(0x110) # p2

payload = p64(0)       # p1's 用戶區
payload += p64(0x81)   # fake chunk size
payload += p64(ptr - 0x18)  # fd, ptr--->p0 + header_size
payload += p64(ptr - 0x10)  # bk
payload += 'a' * (0x80 - len(payload))
payload += p64(0x80)  # pre_size ----- 下一個 chunk p1
payload += p64(0x80)  # size 設置 pre_inused=0
payload += 'b' * 0x70
payload += p64(0x80)
payload += p64(0x21)  # size 設置 pre_inused=1 ---- p1-->next_chunk, 繞過 double free 檢查
edit(2,payload)   # fake chunk

# p1 所在 chunk->pre_inused=0, 向前合並
# 觸發 fake chunk 的 unlink
# ptr--->p0 + header_size, 實現 *ptr = ptr-0x18
del(1) 

image.png

修改 __malloc_hook

一般 malloc 觸發的方式,one_gadgets 由於限制條件不滿足,很可能會失敗

可以使用 malloc_printerr 觸發, 此時恰好 **[esp+0x50]=0 **

__malloc_hook - 0x23 + 0x8 的 內容為 0x000000000000007f , 可以用來繞過 fastbin 分配的檢查
可以 gdb + hexdump 找到類似的位置來偽造 fastbin

Overlap Chunk + Fastbin Attack

總的思路 : 小塊 融合成 大塊, 分配大塊操縱小塊

  • 首先分配兩個 0x90 大小 的 chunk (p0, p1)
  • 釋放掉它們,合並成一個 0x120unsorted bin
  • 分配 0x120chunk (p3) , p3==p1, 而且此時通過 p3 可以修改 p2 的 chunk Overlap Chunk完成
  • 修改 p->size = 0x71 p = p2-0x10pp2 所在chunk的地址
  • 修改 p + 0x70p64(0x70) + p64(0x41) , 設置 pre_inused =1, 使得后面 free(p2) 繞過double free檢測
  • 此時 free(p2) , p2 進入 0x70 大小的 fastbin
  • 再次 free(p1)(此時 p1 所在 chunksize0x120), 得到一個 0x120unsorted bin
  • 再次分配 0x120chunk (p4) , p4==p1
  • 通過 p4 可以修改 p2 指向的 chunkfd__malloc_hook - 0x23 (此時 p2chunk 已經在 0x70fastbin 里面)
  • Fastbin Attack 開始,分配兩次,可以得到 p6 = __malloc_hook -0x13
  • 然后修改 __malloc_hook

image.png

Overlap chunk + fastbin attack + 修改 top chunk
  • 首先通過上面的 Overlap chunk 我們可以修改 p2chunk 的內容
  • 修改 chunk->size = 0x41 , 注意設置 好 chunk->nextchunk 的 pre_inused 位 避免過不了 double free 檢查
  • free(p2) , 此時 p2chunk 進入 0x40fastbin
  • free(p3)malloc(0x110) , 可以再次修改 p2 chunk , 修改 chunk->size = 0x41 and chunk->fd = 0x71
  • malloc(0x30) ,此時 main_arean->fastbinY 中會有一項 的 值 為 p64(0x71)
  • 再次 free(p3)malloc(0x110),修改 p2 chunk, chunk->size = 0x71
  • free(p2) , 此時 p2chunk 進入 0x70fastbin
  • free(p3)malloc(0x110),修改 p2 chunk, 設置chunk->size = 0x71 and chunk->fd = 0x40 fastbinY 的地址附近
  • 分配兩次 0x70chunk, 可以修改 main_arean->top 為 __malloc_hook -0x10 (這里存的指針,值很大)
  • 然后使用 top chunk 進行分配, 就可以拿到 __malloc_hook
Fastbin dup+ Fastbin Attack

在把釋放的塊放進fastbin 時,會檢測也 只檢測 當前 free 的 chunk 和 fastbin 第一項 是否相同 , 如果相同則報 double free 的錯誤。

  • 首先 分配 20x70chunk , p0, p1
  • 釋放 p0, p0 進入 0x70 大小的 fastbin, 此時 p0 為第一項
  • 釋放 p1, p1 進入 0x70 大小的 fastbin, 此時 p1為第一項, p1->fd = p0
  • 再次釋放 p0, 此時 p1fastbin 的 第一項,不會報錯p0 進入 fastbin, 此時 p0 為第一項
  • 分配 0x70chunk p2, p2==p0, 設置 p2->fd = __malloc_hook - 0x23其實就是修改 p0->fd
  • 此時 __malloc_hook - 0x23 成為 0x70 fastbin 的第 3
  • 分配三個 0x70chunk p3, p4, p5p5==__malloc_hook - 0x13
  • 通過 p5 修改 __malloc_hook

image.png

修改 __free_hook

因為 free_hook 上方很大一塊空間都是 \x00, 所以使用 fastbin attack 直接來修改它基本不可能,可以迂回一下,在 free_hook-0xb58 位置會存一些指針,我們通過 fastbin attack 修改 main_arean->top, 到這里然后多用 top_chunk 分配幾次,就可以分配到 free_hook, 然后該 free_hooksystem

Fastbin dup + Fastbin Attack 修改 main_arean->top
  • 首先利用 Fastbin dup 我們可以拿到實現修改 fastbin 中的塊的 fd
  • 由於在 fastbin 中 如果為空,其在 main_arean->fastbinY 里面對應的值為 0x0 , 而堆的地址基本 是 0x5x 開頭的(其在內存就是 xx xx..... 5x), 此時如果在 main_arean->fastbinY 的 相鄰項為 0x0 , 就會出現 5x 00 00 00... , 所以就可以出現 0x000000000000005x ,可以把它作為 fastbinsize 進行 fastbin attack不過作為 fastbin attack 的 size 不能 為 0x55
  • 然后我們就可以修改 main_arean->topfree_hook-0xb58
  • 之后多分配幾次, 既可以分配到 free_hook
  • free_hooksystem
  • free 掉一個 內容為 /bin/sh\x00 的塊

修改 _IO_FILE_plus 結構體 的 vtable

libc 2.24 以下可修改 _IO_FILE_plusvtable 指針到我們可控的位置,進行虛表的偽造。

參考
Pwnable.tw secretgard

off by one

原理

在一些情況下我們可以往指定的 buf 中多寫入 1 個字節的數據 ,這就是 off by one . 這種情況下可以進行利用的原因在於 調用 malloc 分配內存是要 對齊的, 64 位 0x10 字節對齊, 32 位 8 字節對齊,下面均以64位進行說明。如果 malloc(0x28) 則會分配 0x30 字節的 chunk, 除去 0x10 的首部, 我們有 0x20 然后加上下一個 chunk 的 pre_size ,我們就有 0x28 了, 我們知道 pre_size 后面緊跟着就是 size ,所以利用 off by one 可以 修改 下一個 chunksize 字段,同時 在 glibc 中的內存管理 非常依賴這個 size 字段,所以我們可以利用它做一些有趣的事情。

所以當程序中有類似這種不對齊的分配, 就要小心 off by one

利用

普通off by one

在這種情況下,溢出的那個字節不受限制,此時的利用思路就是,多分配幾個 chunk ,然后利用第一個來溢出修改第二個 chunksize改大), 然后 free(chunk_2) , 就可以 overlap chunk 3, 要非常注意 in_used 位的設置

溢出 used 狀態的 chunk

free 時可以獲得包含 chunkunsorted bin

image.png

溢出 free 狀態的 chunk

因為malloc 再分配內存時 不會校驗 unsorted binsize 是否被修改

image.png

Glibc_Adventures-The_Forgotten_Chunks

基於 0ctf 2018 babyheap

信息泄露
  • 首先malloc 4 個 chunk, malloc(0x18)
allocate(0x18)  # 0, 0x20 chunk
allocate(0x38)  # 1, 0x40 chunk----> 溢出修改為 0x91
allocate(0x48)  # 2, 0x50 chunk
allocate(0x18)  # 3, 0x20 chunk
  • 然后在 chunk 0 溢出一個字節,修改 chunk 1 的 size 位 為 0x91 (原來應該為 0x41),這樣一來 通過 chunk 1 索引到的 下一個 chunk 就是 p + 0x90 = chunk 3 (設pchunk 1的地址)

  • 此時 釋放 chunk 1, libc會根據下一個 chunk (這里也就是 chunk3) 的 pre_inused 位來檢查是否double free, 由於 chunk2 原來並沒有被釋放,所以 pre_inused =1 ,於是可以過掉檢查, 此時得到一個 0x90unsorted bin , 同時 chunk2 在 這個 unsorted bin里面, overlap chunk 2

  • 此時再次 malloc(0x38) , 會使用 unsorted bin 進行切割, 所以 在 chunk 2fd, bk 處會寫入 main_arean 的 地址, 打印 chunk 2 的內容就可以 leak libc

漏洞利用

其實可以 overlap chunk 了,就相當於獲得了 堆溢出 的能力,我們可以任意修改 chunk 的數據,此時可以使用 unlink, unsorted bin attack, fastbin attack。 沒有限制內存分配的大小,使用 fastbin attack 即可

這種情況下的 unlink 應該比較簡單,在當前 chunk 偽造好 fd, bk 然后利用 off by one 修改 下一個 chunkpre_size (由於不對齊的分配,這個區域其實屬於當前 chunk ) 和 sizepre_inused0, 然后 free 掉下面那個 chunk ,就可以觸發 unlink

image.png

off by null

在這種情況下,我們只能溢出 \x00 字節, 所以會把 size 變小 同時 inused 位 會被設置為 0

B + 0x100 處要設置好 p64(xxx) + p64(0x41) 關鍵是 pre_inused 位 , free 的時候會檢測這個位

image.png

shrink free chunk size
布局過程
  • 首先分配 3chunk (A B D) , 大小分別為 0x110 , 0x210, 0x110
  • 然后 釋放 B , 此時 D->pre_inused = 0 and D->pre_size = 0x210
  • 修改 B+0x200 處 為 p64(0x200) ,繞過新版 libc 的 chunksize(P) != prev_size (next_chunk(P)) 檢查

image.png

  • 然后分配兩個 chunk (P, K), 大小為 0x110, 0x90
  • 釋放掉 P , 此時 P 會進入 unsorted bin , fd, bk 是有效的 , 原因是 后面合並 D 時需要 unlink
  • 釋放 D , 發現 D->pre_inused=0, 說明前一個 chunk 已經 free, 需要合並。 根據 pre_size 找到 P , 然后 unlink(P) 合並得到一個 0x330unsorted bin, 此時 K 位於 unsorted bin 內部, overlap chunk done

image.png

布局過程中的一些 tips
  • 在第三步 ,釋放 B 之前把 B+0x200 處 設置 p64(0x200) , 因為新版的 libc 會檢驗 chunksize(P) != prev_size (next_chunk(P))

  • off by null 縮小 B 以后,分配 P 其大小不能再 fastbin 的范圍內,后面釋放 D 需要向前合並,會進行 unlink 操作,所以大小 大於 fastbinfree(P)P 會進入 unsorted bin ,此時他的 fd , bk 都是正常的,正常 unlink

參考
how2heap

修改 pre_inused + 向前合並
方案一
  • 首先分配 4chunk (A B C D) , 大小分別為 0x100, 0x100, 0x100, 0x80. 最后那個用於防止 top_chunk 合並
  • 然后釋放 A , 此時 A 進入 unsorted bin , 生成了有效的 FDBK,為了可以在后面的融合中成功 unlink
  • 然后利用 off by null , 設置 C 的 pre_size 和 pre_inused
  • 釋放 C , 系統 根據 C 的 pre_size 找到 A 進行合並,首先 unlink(A) 因為 A 已經在 unsorted bin,不會出錯,然后就會有一個 0x300unsorted bin , 此時 B 位於 該 unsorted bin 的 中間

image.png

方案二

如果程序限制只能在觸發 off by null 之后才能 釋放 A ,需要在 AB 之間多分配一個內存塊 x(0x20), 原因是 觸發 off by nullB 被標識已經 free , 那么此時再 釋放 A 就會對 B 進行 unlink ,此時 Bfdbk 是過不了 檢查的(B已經分配,並已經被用來進行 off by null ) 。

image.png

參考
Libc堆管理機制及漏洞利用技術

總結

對於堆相關的漏洞,不論是 堆溢出,double free, off by one ,uaf 等其最終目的都是為了修改 chunk 的一些管理結構 比如 fd,bk, 然后在后續的堆管理程序處理中實現我們的目的(代碼執行)。

堆溢出

直接可以修改 下一個 chunk 的 元數據 ,然后就是 unsorteb bin attack , fastbin attack 等攻擊手法了

double free

利用一些內存布局,可以實現 overlap chunk ,最后也是實現了 可以修改 chunk 的元數據

off by one

類似於 double free ,實現 overlap chunk 然后改 chunk 元數據


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM